home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / perl5 / Immunix / SubDomain.pm < prev   
Encoding:
Perl POD Document  |  2009-04-08  |  211.5 KB  |  6,639 lines

  1. # $Id: SubDomain.pm 1273 2008-06-03 22:54:55Z jrjohansen $
  2. #
  3. # ----------------------------------------------------------------------
  4. #    Copyright (c) 2006 Novell, Inc. All Rights Reserved.
  5. #
  6. #    This program is free software; you can redistribute it and/or
  7. #    modify it under the terms of version 2 of the GNU General Public
  8. #    License as published by the Free Software Foundation.
  9. #
  10. #    This program is distributed in the hope that it will be useful,
  11. #    but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13. #    GNU General Public License for more details.
  14. #
  15. #    You should have received a copy of the GNU General Public License
  16. #    along with this program; if not, contact Novell, Inc.
  17. #
  18. #    To contact Novell about this file by physical or electronic mail,
  19. #    you may find current contact information at www.novell.com.
  20. # ----------------------------------------------------------------------
  21.  
  22. package Immunix::SubDomain;
  23.  
  24. use strict;
  25. use warnings;
  26.  
  27. use Carp;
  28. use Cwd qw(cwd realpath);
  29. use File::Basename;
  30. use File::Temp qw/ tempfile tempdir /;
  31. use Data::Dumper;
  32.  
  33. use Locale::gettext;
  34. use POSIX;
  35. use Storable qw(dclone);
  36.  
  37. use Term::ReadKey;
  38.  
  39. use Immunix::Severity;
  40. use Immunix::Repository;
  41. use Immunix::Config;
  42. use LibAppArmor;
  43.  
  44. require Exporter;
  45. our @ISA    = qw(Exporter);
  46. our @EXPORT = qw(
  47.     %sd
  48.     %qualifiers
  49.     %include
  50.     %helpers
  51.  
  52.     $filename
  53.     $profiledir
  54.     $parser
  55.     $logger
  56.     $UI_Mode
  57.     $running_under_genprof
  58.  
  59.     which
  60.     getprofilefilename
  61.     get_full_path
  62.     fatal_error
  63.     get_pager
  64.  
  65.     getprofileflags
  66.     setprofileflags
  67.     complain
  68.     enforce
  69.  
  70.     autodep
  71.     reload
  72.  
  73.     UI_GetString
  74.     UI_GetFile
  75.     UI_YesNo
  76.     UI_ShortMessage
  77.     UI_LongMessage
  78.  
  79.     UI_Important
  80.     UI_Info
  81.     UI_PromptUser
  82.     display_changes
  83.     getkey
  84.  
  85.     do_logprof_pass
  86.  
  87.     loadincludes
  88.     readprofile
  89.     readprofiles
  90.     writeprofile
  91.     serialize_profile
  92.     attach_profile_data
  93.     parse_repo_profile
  94.     activate_repo_profiles
  95.  
  96.     check_for_subdomain
  97.  
  98.     setup_yast
  99.     shutdown_yast
  100.     GetDataFromYast
  101.     SendDataToYast
  102.  
  103.     checkProfileSyntax
  104.     checkIncludeSyntax
  105.     check_qualifiers
  106.  
  107.     isSkippableFile
  108. );
  109.  
  110. our $confdir = "/etc/apparmor";
  111.  
  112. our $running_under_genprof = 0;
  113.  
  114. our $DEBUGGING;
  115.  
  116. our $unimplemented_warning = 0;
  117.  
  118. # keep track of if we're running under yast or not - default to text mode
  119. our $UI_Mode = "text";
  120.  
  121. our $sevdb;
  122.  
  123. # initialize Term::ReadLine if it's available
  124. our $term;
  125. eval {
  126.     require Term::ReadLine;
  127.     import Term::ReadLine;
  128.     $term = new Term::ReadLine 'AppArmor';
  129. };
  130.  
  131. # initialize the local poo
  132. setlocale(LC_MESSAGES, "")
  133.     unless defined(LC_MESSAGES);
  134. textdomain("apparmor-utils");
  135.  
  136. # where do we get our log messages from?
  137. our $filename;
  138.  
  139. our $cfg;
  140. our $repo_cfg;
  141.  
  142. our $parser;
  143. our $ldd;
  144. our $logger;
  145. our $profiledir;
  146. our $extraprofiledir;
  147.  
  148. # we keep track of the included profile fragments with %include
  149. my %include;
  150.  
  151. my %existing_profiles;
  152.  
  153. our $seenevents = 0;
  154.  
  155.  
  156. # these are globs that the user specifically entered.  we'll keep track of
  157. # them so that if one later matches, we'll suggest it again.
  158. our @userglobs;
  159.  
  160. ### THESE VARIABLES ARE USED WITHIN LOGPROF
  161. our %t;
  162. our %transitions;
  163. our %sd;    # we keep track of the original profiles in %sd
  164. our %original_sd;
  165. our %extras;  # inactive profiles from extras
  166.  
  167. my @log;
  168. my %pid;
  169.  
  170. my %seen;
  171. my %profilechanges;
  172. my %prelog;
  173. my %log;
  174. my %changed;
  175. my @created;
  176. my %skip;
  177. our %helpers;    # we want to preserve this one between passes
  178.  
  179. ### THESE VARIABLES ARE USED WITHIN LOGPROF
  180.  
  181. my %filelist;   # file level stuff including variables in config files
  182.  
  183. my $AA_MAY_EXEC = 1;
  184. my $AA_MAY_WRITE = 2;
  185. my $AA_MAY_READ = 4;
  186. my $AA_MAY_APPEND = 8;
  187. my $AA_MAY_LINK = 16;
  188. my $AA_MAY_LOCK = 32;
  189. my $AA_EXEC_MMAP = 64;
  190. my $AA_EXEC_UNSAFE = 128;
  191. my $AA_EXEC_INHERIT = 256;
  192. my $AA_EXEC_UNCONFINED = 512;
  193. my $AA_EXEC_PROFILE = 1024;
  194. my $AA_EXEC_CHILD = 2048;
  195. my $AA_EXEC_NT = 4096;
  196. my $AA_LINK_SUBSET = 8192;
  197.  
  198. my $AA_OTHER_SHIFT = 14;
  199. my $AA_USER_MASK = 16384 -1;
  200.  
  201. my $AA_EXEC_TYPE = $AA_MAY_EXEC | $AA_EXEC_UNSAFE | $AA_EXEC_INHERIT |
  202.             $AA_EXEC_UNCONFINED | $AA_EXEC_PROFILE | $AA_EXEC_CHILD | $AA_EXEC_NT;
  203.  
  204. my $ALL_AA_EXEC_TYPE = $AA_EXEC_TYPE;
  205.  
  206. my %MODE_HASH = (
  207.     x => $AA_MAY_EXEC,
  208.     X => $AA_MAY_EXEC,
  209.     w => $AA_MAY_WRITE,
  210.     W => $AA_MAY_WRITE,
  211.     r => $AA_MAY_READ,
  212.     R => $AA_MAY_READ,
  213.     a => $AA_MAY_APPEND,
  214.     A => $AA_MAY_APPEND,
  215.     l => $AA_MAY_LINK,
  216.     L => $AA_MAY_LINK,
  217.     k => $AA_MAY_LOCK,
  218.     K => $AA_MAY_LOCK,
  219.     m => $AA_EXEC_MMAP,
  220.     M => $AA_EXEC_MMAP,
  221. #   Unsafe => 128,
  222.     i => $AA_EXEC_INHERIT,
  223.     I => $AA_EXEC_INHERIT,
  224.     u => $AA_EXEC_UNCONFINED + $AA_EXEC_UNSAFE,        # U + Unsafe
  225.     U => $AA_EXEC_UNCONFINED,
  226.     p => $AA_EXEC_PROFILE + $AA_EXEC_UNSAFE,        # P + Unsafe
  227.     P => $AA_EXEC_PROFILE,
  228.     c => $AA_EXEC_CHILD + $AA_EXEC_UNSAFE,
  229.     C => $AA_EXEC_CHILD,
  230.     n => $AA_EXEC_NT + $AA_EXEC_UNSAFE,
  231.     N => $AA_EXEC_NT,
  232.     );
  233.  
  234. sub debug ($) {
  235.     my $message = shift;
  236.     chomp($message);
  237.  
  238.     print DEBUG "$message\n" if $DEBUGGING;
  239. }
  240.  
  241. my %arrows = ( A => "UP", B => "DOWN", C => "RIGHT", D => "LEFT" );
  242.  
  243. sub getkey {
  244.     # change to raw mode
  245.     ReadMode(4);
  246.  
  247.     my $key = ReadKey(0);
  248.  
  249.     # decode arrow key control sequences
  250.     if ($key eq "\x1B") {
  251.         $key = ReadKey(0);
  252.         if ($key eq "[") {
  253.             $key = ReadKey(0);
  254.             if ($arrows{$key}) {
  255.                 $key = $arrows{$key};
  256.             }
  257.         }
  258.     }
  259.  
  260.     # return to cooked mode
  261.     ReadMode(0);
  262.     return $key;
  263. }
  264.  
  265. BEGIN {
  266.     # set things up to log extra info if they want...
  267.     if ($ENV{LOGPROF_DEBUG}) {
  268.         $DEBUGGING = 1;
  269.         open(DEBUG, ">/var/log/apparmor/logprof_debug_$$.log");
  270.         my $oldfd = select(DEBUG);
  271.         $| = 1;
  272.         select($oldfd);
  273.     } else {
  274.         $DEBUGGING = 0;
  275.     }
  276. }
  277.  
  278. END {
  279.     $DEBUGGING && debug "Exiting...";
  280.  
  281.     # close the debug log if necessary
  282.     close(DEBUG) if $DEBUGGING;
  283. }
  284.  
  285. # returns true if the specified program contains references to LD_PRELOAD or
  286. # LD_LIBRARY_PATH to give the PX/UX code better suggestions
  287. sub check_for_LD_XXX ($) {
  288.     my $file = shift;
  289.  
  290.     return undef unless -f $file;
  291.  
  292.     # limit our checking to programs/scripts under 10k to speed things up a bit
  293.     my $size = -s $file;
  294.     return undef unless ($size && $size < 10000);
  295.  
  296.     my $found = undef;
  297.     if (open(F, $file)) {
  298.         while (<F>) {
  299.             $found = 1 if /LD_(PRELOAD|LIBRARY_PATH)/;
  300.         }
  301.         close(F);
  302.     }
  303.  
  304.     return $found;
  305. }
  306.  
  307. sub fatal_error ($) {
  308.     my $message = shift;
  309.  
  310.     my $details = "$message\n";
  311.  
  312.     if ($DEBUGGING) {
  313.  
  314.         # we'll include the stack backtrace if we're debugging...
  315.         $details = Carp::longmess($message);
  316.  
  317.         # write the error to the log
  318.         print DEBUG $details;
  319.     }
  320.  
  321.     # we'll just shoot ourselves in the head if it was one of the yast
  322.     # interface functions that ran into an error.  it gets really ugly if
  323.     # the yast frontend goes away and we try to notify the user of that
  324.     # problem by trying to send the yast frontend a pretty dialog box
  325.     my $caller = (caller(1))[3];
  326.  
  327.     exit 1 if defined($caller) && $caller =~ /::(Send|Get)Data(To|From)Yast$/;
  328.  
  329.     # tell the user what the hell happened
  330.     UI_Important($details);
  331.  
  332.     # make sure the frontend exits cleanly...
  333.     shutdown_yast();
  334.  
  335.     # die a horrible flaming death
  336.     exit 1;
  337. }
  338.  
  339. sub setup_yast {
  340.  
  341.     # set up the yast connection if we're running under yast...
  342.     if ($ENV{YAST_IS_RUNNING}) {
  343.  
  344.         # load the yast module if available.
  345.         eval { require ycp; };
  346.         unless ($@) {
  347.             import ycp;
  348.  
  349.             $UI_Mode = "yast";
  350.  
  351.             # let the frontend know that we're starting
  352.             SendDataToYast({
  353.                 type   => "initial_handshake",
  354.                 status => "backend_starting"
  355.             });
  356.  
  357.             # see if the frontend is just starting up also...
  358.             my ($ypath, $yarg) = GetDataFromYast();
  359.             unless ($yarg
  360.                 && (ref($yarg)      eq "HASH")
  361.                 && ($yarg->{type}   eq "initial_handshake")
  362.                 && ($yarg->{status} eq "frontend_starting"))
  363.             {
  364.  
  365.                 # something's broken, die a horrible, painful death
  366.                 fatal_error "Yast frontend is out of sync from backend agent.";
  367.             }
  368.             $DEBUGGING && debug "Initial handshake ok";
  369.  
  370.             # the yast connection seems to be working okay
  371.             return 1;
  372.         }
  373.  
  374.     }
  375.  
  376.     # couldn't init yast
  377.     return 0;
  378. }
  379.  
  380. sub shutdown_yast {
  381.     if ($UI_Mode eq "yast") {
  382.         SendDataToYast({ type => "final_shutdown" });
  383.         my ($ypath, $yarg) = GetDataFromYast();
  384.     }
  385. }
  386.  
  387. sub check_for_subdomain () {
  388.  
  389.     my ($support_subdomainfs, $support_securityfs);
  390.     if (open(MOUNTS, "/proc/filesystems")) {
  391.         while (<MOUNTS>) {
  392.             $support_subdomainfs = 1 if m/subdomainfs/;
  393.             $support_securityfs  = 1 if m/securityfs/;
  394.         }
  395.         close(MOUNTS);
  396.     }
  397.  
  398.     my $sd_mountpoint = "";
  399.     if (open(MOUNTS, "/proc/mounts")) {
  400.         while (<MOUNTS>) {
  401.             if ($support_subdomainfs) {
  402.                 $sd_mountpoint = $1 if m/^\S+\s+(\S+)\s+subdomainfs\s/;
  403.             } elsif ($support_securityfs) {
  404.                 if (m/^\S+\s+(\S+)\s+securityfs\s/) {
  405.                     if (-e "$1/apparmor") {
  406.                         $sd_mountpoint = "$1/apparmor";
  407.                     } elsif (-e "$1/subdomain") {
  408.                         $sd_mountpoint = "$1/subdomain";
  409.                     }
  410.                 }
  411.             }
  412.         }
  413.         close(MOUNTS);
  414.     }
  415.  
  416.     # make sure that subdomain is actually mounted there
  417.     $sd_mountpoint = undef unless -f "$sd_mountpoint/profiles";
  418.  
  419.     return $sd_mountpoint;
  420. }
  421.  
  422. sub which ($) {
  423.     my $file = shift;
  424.  
  425.     foreach my $dir (split(/:/, $ENV{PATH})) {
  426.         return "$dir/$file" if -x "$dir/$file";
  427.     }
  428.  
  429.     return undef;
  430. }
  431.  
  432. # we need to convert subdomain regexps to perl regexps
  433. sub convert_regexp ($) {
  434.     my $regexp = shift;
  435.  
  436.     # escape regexp-special characters we don't support
  437.     $regexp =~ s/(?<!\\)(\.|\+|\$)/\\$1/g;
  438.  
  439.     # * and ** globs can't collapse to match an empty string when they're
  440.     # the only part of the glob at a specific directory level, which
  441.     # complicates things a little.
  442.  
  443.     # ** globs match multiple directory levels
  444.     $regexp =~ s{(?<!\\)\*\*+}{
  445.       my ($pre, $post) = ($`, $');
  446.       if (($pre =~ /\/$/) && (!$post || $post =~ /^\//)) {
  447.         'SD_INTERNAL_MULTI_REQUIRED';
  448.       } else {
  449.         'SD_INTERNAL_MULTI_OPTIONAL';
  450.       }
  451.     }gex;
  452.  
  453.     # convert * globs to match anything at the current path level
  454.     $regexp =~ s{(?<!\\)\*}{
  455.       my ($pre, $post) = ($`, $');
  456.       if (($pre =~ /\/$/) && (!$post || $post =~ /^\//)) {
  457.         'SD_INTERNAL_SINGLE_REQUIRED';
  458.       } else {
  459.         'SD_INTERNAL_SINGLE_OPTIONAL';
  460.       }
  461.     }gex;
  462.  
  463.     # convert ? globs to match a single character at current path level
  464.     $regexp =~ s/(?<!\\)\?/[^\/]/g;
  465.  
  466.     # convert {foo,baz} to (foo|baz)
  467.     $regexp =~ y/\{\}\,/\(\)\|/ if $regexp =~ /\{.*\,.*\}/;
  468.  
  469.     # convert internal markers to their appropriate regexp equivalents
  470.     $regexp =~ s/SD_INTERNAL_SINGLE_OPTIONAL/[^\/]*/g;
  471.     $regexp =~ s/SD_INTERNAL_SINGLE_REQUIRED/[^\/]+/g;
  472.     $regexp =~ s/SD_INTERNAL_MULTI_OPTIONAL/.*/g;
  473.     $regexp =~ s/SD_INTERNAL_MULTI_REQUIRED/[^\/].*/g;
  474.  
  475.     return $regexp;
  476. }
  477.  
  478. sub get_full_path ($) {
  479.     my $originalpath = shift;
  480.  
  481.     my $path = $originalpath;
  482.  
  483.     # keep track so we can break out of loops
  484.     my $linkcount = 0;
  485.  
  486.     # if we don't have any directory foo, look in the current dir
  487.     $path = cwd() . "/$path" if $path !~ m/\//;
  488.  
  489.     # beat symlinks into submission
  490.     while (-l $path) {
  491.  
  492.         if ($linkcount++ > 64) {
  493.             fatal_error "Followed too many symlinks resolving $originalpath";
  494.         }
  495.  
  496.         # split out the directory/file components
  497.         if ($path =~ m/^(.*)\/(.+)$/) {
  498.             my ($dir, $file) = ($1, $2);
  499.  
  500.             # figure out where the link is pointing...
  501.             my $link = readlink($path);
  502.             if ($link =~ /^\//) {
  503.                 # if it's an absolute link, just replace it
  504.                 $path = $link;
  505.             } else {
  506.                 # if it's relative, let abs_path handle it
  507.                 $path = $dir . "/$link";
  508.             }
  509.         }
  510.     }
  511.  
  512.     if (-f $path) {
  513.         my ($dir, $file) = $path =~ m/^(.*)\/(.+)$/;
  514.         $path = realpath($dir) . "/$file";
  515.     } else {
  516.         $path = realpath($path);
  517.     }
  518.  
  519.     return $path;
  520. }
  521.  
  522. sub findexecutable ($) {
  523.     my $bin = shift;
  524.  
  525.     my $fqdbin;
  526.     if (-e $bin) {
  527.         $fqdbin = get_full_path($bin);
  528.         chomp($fqdbin);
  529.     } else {
  530.         if ($bin !~ /\//) {
  531.             my $which = which($bin);
  532.             if ($which) {
  533.                 $fqdbin = get_full_path($which);
  534.             }
  535.         }
  536.     }
  537.  
  538.     unless ($fqdbin && -e $fqdbin) {
  539.         return undef;
  540.     }
  541.  
  542.     return $fqdbin;
  543. }
  544.  
  545. sub name_to_prof_filename($) {
  546.     my $bin    = shift;
  547.     my $filename;
  548.  
  549.     unless ($bin =~ /^($profiledir)/) {
  550.     my $fqdbin = findexecutable($bin);
  551.     if ($fqdbin) {
  552.         $filename = getprofilefilename($fqdbin);
  553.         return ($filename, $fqdbin) if -f $filename;
  554.     }
  555.     }
  556.  
  557.     if ($bin =~ /^$profiledir(.*)/) {
  558.     my $profile = $1;
  559.     return ($bin, $profile);
  560.     } elsif ($bin =~ /^\//) {
  561.     $filename = getprofilefilename($bin);
  562.     return ($filename, $bin);
  563.     } else {
  564.     # not an absolute path try it as a profile_
  565.     $bin = $1 if ($bin !~ /^profile_(.*)/);
  566.     $filename = getprofilefilename($bin);
  567.     return ($filename, "profile_${bin}");
  568.     }
  569.     return undef;
  570. }
  571.  
  572. sub complain ($) {
  573.     my $bin = shift;
  574.  
  575.     return if (!$bin);
  576.  
  577.     my ($filename, $name) = name_to_prof_filename($bin)
  578.     or fatal_error(sprintf(gettext('Can\'t find %s.'), $bin));
  579.  
  580.     UI_Info(sprintf(gettext('Setting %s to complain mode.'), $name));
  581.  
  582.     setprofileflags($filename, "complain");
  583. }
  584.  
  585. sub enforce ($) {
  586.     my $bin = shift;
  587.  
  588.     return if (!$bin);
  589.  
  590.     my ($filename, $name) = name_to_prof_filename($bin)
  591.     or fatal_error(sprintf(gettext('Can\'t find %s.'), $bin));
  592.  
  593.     UI_Info(sprintf(gettext('Setting %s to enforce mode.'), $name));
  594.  
  595.     setprofileflags($filename, "");
  596. }
  597.  
  598. sub head ($) {
  599.     my $file = shift;
  600.  
  601.     my $first = "";
  602.     if (open(FILE, $file)) {
  603.         $first = <FILE>;
  604.         close(FILE);
  605.     }
  606.  
  607.     return $first;
  608. }
  609.  
  610. sub get_output (@) {
  611.     my ($program, @args) = @_;
  612.  
  613.     my $ret = -1;
  614.  
  615.     my $pid;
  616.     my @output;
  617.  
  618.     if (-x $program) {
  619.         $pid = open(KID_TO_READ, "-|");
  620.         unless (defined $pid) {
  621.             fatal_error "can't fork: $!";
  622.         }
  623.  
  624.         if ($pid) {
  625.             while (<KID_TO_READ>) {
  626.                 chomp;
  627.                 push @output, $_;
  628.             }
  629.             close(KID_TO_READ);
  630.             $ret = $?;
  631.         } else {
  632.             ($>, $)) = ($<, $();
  633.             open(STDERR, ">&STDOUT")
  634.               || fatal_error "can't dup stdout to stderr";
  635.             exec($program, @args) || fatal_error "can't exec program: $!";
  636.  
  637.             # NOTREACHED
  638.         }
  639.     }
  640.  
  641.     return ($ret, @output);
  642. }
  643.  
  644. sub get_reqs ($) {
  645.     my $file = shift;
  646.  
  647.     my @reqs;
  648.     my ($ret, @ldd) = get_output($ldd, $file);
  649.  
  650.     if ($ret == 0) {
  651.         for my $line (@ldd) {
  652.             last if $line =~ /not a dynamic executable/;
  653.             last if $line =~ /cannot read header/;
  654.             last if $line =~ /statically linked/;
  655.  
  656.             # avoid new kernel 2.6 poo
  657.             next if $line =~ /linux-(gate|vdso(32|64)).so/;
  658.  
  659.             if ($line =~ /^\s*\S+ => (\/\S+)/) {
  660.                 push @reqs, $1;
  661.             } elsif ($line =~ /^\s*(\/\S+)/) {
  662.                 push @reqs, $1;
  663.             }
  664.         }
  665.     }
  666.  
  667.     return @reqs;
  668. }
  669.  
  670. sub handle_binfmt ($$) {
  671.     my ($profile, $fqdbin) = @_;
  672.  
  673.     my %reqs;
  674.     my @reqs = get_reqs($fqdbin);
  675.  
  676.     while (my $library = shift @reqs) {
  677.  
  678.         $library = get_full_path($library);
  679.  
  680.         push @reqs, get_reqs($library) unless $reqs{$library}++;
  681.  
  682.         # does path match anything pulled in by includes in original profile?
  683.         my $combinedmode = match_prof_incs_to_path($profile, 'allow', $library);
  684.  
  685.         # if we found any matching entries, do the modes match?
  686.         next if $combinedmode;
  687.  
  688.         $library = globcommon($library);
  689.         chomp $library;
  690.         next unless $library;
  691.  
  692.         $profile->{allow}{path}->{$library}{mode} = str_to_mode("mr");
  693.         $profile->{allow}{path}->{$library}{audit} = 0;
  694.     }
  695. }
  696.  
  697. sub get_inactive_profile {
  698.     my $fqdbin = shift;
  699.     if ( $extras{$fqdbin} ) {
  700.         return {$fqdbin => $extras{$fqdbin}};
  701.     }
  702. }
  703.  
  704.  
  705.  
  706. sub create_new_profile {
  707.     my $fqdbin = shift;
  708.  
  709.     my $profile;
  710.     if ($fqdbin =~ /^\// ) {
  711.     $profile = {
  712.         $fqdbin => {
  713.         flags   => "complain",
  714.         include => { "abstractions/base" => 1    },
  715.         path    => { $fqdbin => { mode => str_to_mode("mr") } },
  716.         }
  717.     };
  718.     } else {
  719.     $profile = {
  720.         $fqdbin => {
  721.         flags   => "complain",
  722.         include => { "abstractions/base" => 1    },
  723.         }
  724.     };
  725.     }
  726.  
  727.     # if the executable exists on this system, pull in extra dependencies
  728.     if (-f $fqdbin) {
  729.         my $hashbang = head($fqdbin);
  730.         if ($hashbang && $hashbang =~ /^#!\s*(\S+)/) {
  731.             my $interpreter = get_full_path($1);
  732.             $profile->{$fqdbin}{allow}{path}->{$interpreter}{mode} = str_to_mode("ix");
  733.             $profile->{$fqdbin}{allow}{path}->{$interpreter}{audit} = 0;
  734.             if ($interpreter =~ /perl/) {
  735.                 $profile->{$fqdbin}{include}->{"abstractions/perl"} = 1;
  736.             } elsif ($interpreter =~ m/\/bin\/(bash|sh)/) {
  737.                 $profile->{$fqdbin}{include}->{"abstractions/bash"} = 1;
  738.             }
  739.             handle_binfmt($profile->{$fqdbin}, $interpreter);
  740.         } else {
  741.           handle_binfmt($profile->{$fqdbin}, $fqdbin);
  742.         }
  743.     }
  744.  
  745.     # create required infrastructure hats if it's a known change_hat app
  746.     for my $hatglob (keys %{$cfg->{required_hats}}) {
  747.         if ($fqdbin =~ /$hatglob/) {
  748.             for my $hat (sort split(/\s+/, $cfg->{required_hats}{$hatglob})) {
  749.                 $profile->{$hat} = { flags => "complain" };
  750.             }
  751.         }
  752.     }
  753.     push @created, $fqdbin;
  754.     return { $fqdbin => $profile };
  755. }
  756.  
  757. sub delete_profile ($) {
  758.     my $profile = shift;
  759.     my $profilefile = getprofilefilename( $profile );
  760.     if ( -e $profilefile ) {
  761.       unlink( $profilefile );
  762.     }
  763.     if ( defined $sd{$profile} ) {
  764.         delete $sd{$profile};
  765.     }
  766. }
  767.  
  768. sub get_profile {
  769.     my $fqdbin = shift;
  770.     my $profile_data;
  771.  
  772.     my $distro     = $cfg->{repository}{distro};
  773.     my $repo_url   = $cfg->{repository}{url};
  774.     my @profiles;
  775.     my %profile_hash;
  776.  
  777.     if (repo_is_enabled()) {
  778.        my $results;
  779.        UI_BusyStart( gettext("Connecting to repository.....") );
  780.  
  781.        my ($status_ok,$ret) =
  782.            fetch_profiles_by_name($repo_url, $distro, $fqdbin );
  783.        UI_BusyStop();
  784.        if ( $status_ok ) {
  785.            %profile_hash = %$ret;
  786.        } else {
  787.            my $errmsg =
  788.              sprintf(gettext("WARNING: Error fetching profiles from the repository:\n%s\n"),
  789.                      $ret?$ret:gettext("UNKNOWN ERROR"));
  790.            UI_Important( $errmsg );
  791.        }
  792.     }
  793.  
  794.     my $inactive_profile = get_inactive_profile($fqdbin);
  795.     if ( defined $inactive_profile && $inactive_profile ne "" ) {
  796.         # set the profile to complain mode
  797.         my $uname = gettext( "Inactive local profile for ") . $fqdbin;
  798.         $inactive_profile->{$fqdbin}{$fqdbin}{flags} = "complain";
  799.     # inactive profiles store where they came from
  800.     delete $inactive_profile->{$fqdbin}{$fqdbin}{filename};
  801.         $profile_hash{$uname} =
  802.             {
  803.               "username"     => $uname,
  804.               "profile_type" => "INACTIVE_LOCAL",
  805.               "profile"      => serialize_profile($inactive_profile->{$fqdbin},
  806.                                   $fqdbin
  807.                                 ),
  808.               "profile_data" => $inactive_profile,
  809.             };
  810.     }
  811.  
  812.     return undef if ( keys %profile_hash == 0 ); # No repo profiles, no inactive
  813.                                             # profile
  814.     my @options;
  815.     my @tmp_list;
  816.     my $preferred_present = 0;
  817.     my $preferred_user  = $cfg->{repository}{preferred_user} || "NOVELL";
  818.  
  819.     foreach my $p ( keys %profile_hash ) {
  820.         if ( $profile_hash{$p}->{username} eq $preferred_user ) {
  821.              $preferred_present = 1;
  822.         } else {
  823.             push @tmp_list, $profile_hash{$p}->{username};
  824.         }
  825.     }
  826.  
  827.     if ( $preferred_present ) {
  828.         push  @options, $preferred_user;
  829.     }
  830.     push  @options, @tmp_list;
  831.  
  832.     my $q = {};
  833.     $q->{headers} = [];
  834.     push @{ $q->{headers} }, gettext("Profile"), $fqdbin;
  835.  
  836.     $q->{functions} = [ "CMD_VIEW_PROFILE", "CMD_USE_PROFILE",
  837.                         "CMD_CREATE_PROFILE", "CMD_ABORT", "CMD_FINISHED" ];
  838.  
  839.     $q->{default} = "CMD_VIEW_PROFILE";
  840.  
  841.     $q->{options}  = [@options];
  842.     $q->{selected} = 0;
  843.  
  844.     my ($p, $ans, $arg);
  845.     do {
  846.         ($ans, $arg) = UI_PromptUser($q);
  847.         $p = $profile_hash{$options[$arg]};
  848.         for (my $i = 0; $i < scalar(@options); $i++) {
  849.             if ($options[$i] eq $options[$arg]) {
  850.                 $q->{selected} = $i;
  851.             }
  852.         }
  853.  
  854.         if ($ans eq "CMD_VIEW_PROFILE") {
  855.             if ($UI_Mode eq "yast") {
  856.                 SendDataToYast(
  857.                     {
  858.                         type         => "dialog-view-profile",
  859.                         user         => $options[$arg],
  860.                         profile      => $p->{profile},
  861.                         profile_type => $p->{profile_type}
  862.                     }
  863.                 );
  864.                 my ($ypath, $yarg) = GetDataFromYast();
  865.             } else {
  866.                 my $pager = get_pager();
  867.                 open(PAGER, "| $pager");
  868.                 print PAGER gettext("Profile submitted by") .
  869.                                     " $options[$arg]:\n\n" . $p->{profile} . "\n\n";
  870.                 close(PAGER);
  871.             }
  872.         } elsif ($ans eq "CMD_USE_PROFILE") {
  873.             if ( $p->{profile_type} eq "INACTIVE_LOCAL" ) {
  874.                 $profile_data = $p->{profile_data};
  875.                 push @created, $fqdbin; # This really is ugly here
  876.                                         # need to find a better place to mark
  877.                                         # this as newly created
  878.             } else {
  879.                 $profile_data =
  880.                     parse_repo_profile($fqdbin, $repo_url, $p);
  881.             }
  882.         }
  883.     } until ($ans =~ /^CMD_(USE_PROFILE|CREATE_PROFILE)$/);
  884.  
  885.     return $profile_data;
  886. }
  887.  
  888. sub activate_repo_profiles ($$$) {
  889.     my ($url,$profiles,$complain) = @_;
  890.  
  891.     readprofiles();
  892.     eval {
  893.         for my $p ( @$profiles ) {
  894.             my $pname = $p->[0];
  895.             my $profile_data = parse_repo_profile( $pname, $url, $p->[1] );
  896.             attach_profile_data(\%sd, $profile_data);
  897.             writeprofile($pname);
  898.             if ( $complain ) {
  899.                 my $filename = getprofilefilename($pname);
  900.                 setprofileflags($filename, "complain");
  901.                 UI_Info(sprintf(gettext('Setting %s to complain mode.'),
  902.                                         $pname));
  903.             }
  904.         }
  905.     };
  906.     # if there were errors....
  907.     if ($@) {
  908.         $@ =~ s/\n$//;
  909.         print STDERR sprintf(gettext("Error activating profiles: %s\n"), $@);
  910.     }
  911. }
  912.  
  913. sub autodep_base($$) {
  914.     my ($bin, $pname) = @_;
  915.     %extras = ();
  916.  
  917.     $bin = $pname if (! $bin) && ($pname =~ /^\//);
  918.  
  919.     unless ($repo_cfg || not defined $cfg->{repository}{url}) {
  920.         $repo_cfg = read_config("repository.conf");
  921.         if ( (not defined $repo_cfg->{repository}) ||
  922.              ($repo_cfg->{repository}{enabled} eq "later") ) {
  923.                 UI_ask_to_enable_repo();
  924.         }
  925.     }
  926.  
  927.     my $fqdbin;
  928.     if ($bin) {
  929.     # findexecutable() might fail if we're running on a different system
  930.     # than the logs were collected on.  ugly.  we'll just hope for the best.
  931.     $fqdbin = findexecutable($bin) || $bin;
  932.  
  933.     # try to make sure we have a full path in case findexecutable failed
  934.     return unless $fqdbin =~ /^\//;
  935.  
  936.     # ignore directories
  937.     return if -d $fqdbin;
  938.     }
  939.  
  940.     $pname = $fqdbin if $fqdbin;
  941.  
  942.     my $profile_data;
  943.  
  944.     readinactiveprofiles(); # need to read the profiles to see if an
  945.                             # inactive local profile is present
  946.     $profile_data = eval { get_profile($pname) };
  947.  
  948.     unless ($profile_data) {
  949.         $profile_data = create_new_profile($pname);
  950.     }
  951.  
  952.     my $file = getprofilefilename($pname);
  953.  
  954.     # stick the profile into our data structure.
  955.     attach_profile_data(\%sd, $profile_data);
  956.     # and store a "clean" version also so we can display the changes we've
  957.     # made during this run
  958.     attach_profile_data(\%original_sd, $profile_data);
  959.  
  960.     if (-f "$profiledir/tunables/global") {
  961.         unless (exists $filelist{$file}) {
  962.             $filelist{$file} = { };
  963.         }
  964.         $filelist{$file}{include}{'tunables/global'} = 1; # sorry
  965.     }
  966.  
  967.     # write out the profile...
  968.     writeprofile_ui_feedback($pname);
  969. }
  970.  
  971. sub autodep ($) {
  972.     my $bin = shift;
  973.     return autodep_base($bin, "");
  974. }
  975.  
  976. sub getprofilefilename ($) {
  977.     my $profile = shift;
  978.  
  979.     my $filename = $profile;
  980.     if ($filename =~ /^\//) {
  981.     $filename =~ s/^\///;                              # strip leading /
  982.     } else {
  983.     $filename = "profile_$filename";
  984.     }
  985.     $filename =~ s/\//./g;                            # convert /'s to .'s
  986.  
  987.     return "$profiledir/$filename";
  988. }
  989.  
  990. sub setprofileflags ($$) {
  991.     my $filename = shift;
  992.     my $newflags = shift;
  993.  
  994.     if (open(PROFILE, "$filename")) {
  995.         if (open(NEWPROFILE, ">$filename.new")) {
  996.             while (<PROFILE>) {
  997.                 if (m/^\s*("??\/.+?"??)\s+(flags=\(.+\)\s+)*\{\s*$/) {
  998.                     my ($binary, $flags) = ($1, $2);
  999.  
  1000.                     if ($newflags) {
  1001.                         $_ = "$binary flags=($newflags) {\n";
  1002.                     } else {
  1003.                         $_ = "$binary {\n";
  1004.                     }
  1005.                 } elsif (m/^(\s*\^\S+)\s+(flags=\(.+\)\s+)*\{\s*$/) {
  1006.                     my ($hat, $flags) = ($1, $2);
  1007.  
  1008.                     if ($newflags) {
  1009.                         $_ = "$hat flags=($newflags) {\n";
  1010.                     } else {
  1011.                         $_ = "$hat {\n";
  1012.                     }
  1013.                 }
  1014.                 print NEWPROFILE;
  1015.             }
  1016.             close(NEWPROFILE);
  1017.             rename("$filename.new", "$filename");
  1018.         }
  1019.         close(PROFILE);
  1020.     }
  1021. }
  1022.  
  1023. sub profile_exists($) {
  1024.     my $program = shift || return 0;
  1025.  
  1026.     # if it's already in the cache, return true
  1027.     return 1 if $existing_profiles{$program};
  1028.  
  1029.     # if the profile exists, mark it in the cache and return true
  1030.     my $profile = getprofilefilename($program);
  1031.     if (-e $profile) {
  1032.         $existing_profiles{$program} = 1;
  1033.         return 1;
  1034.     }
  1035.  
  1036.     # couldn't find a profile, so we'll return false
  1037.     return 0;
  1038. }
  1039.  
  1040. sub sync_profiles {
  1041.  
  1042.     my ($user, $pass) = get_repo_user_pass();
  1043.     return unless ( $user && $pass );
  1044.  
  1045.     my @repo_profiles;
  1046.     my @changed_profiles;
  1047.     my @new_profiles;
  1048.     my $serialize_opts = { };
  1049.     my ($status_ok,$ret) =
  1050.         fetch_profiles_by_user($cfg->{repository}{url},
  1051.                                $cfg->{repository}{distro},
  1052.                                $user
  1053.                               );
  1054.     if ( !$status_ok ) {
  1055.         my $errmsg =
  1056.           sprintf(gettext("WARNING: Error syncronizing profiles with the repository:\n%s\n"),
  1057.                   $ret?$ret:gettext("UNKNOWN ERROR"));
  1058.         UI_Important($errmsg);
  1059.         return;
  1060.     } else {
  1061.         my $users_repo_profiles = $ret;
  1062.         $serialize_opts->{NO_FLAGS} = 1;
  1063.         #
  1064.         # Find changes made to non-repo profiles
  1065.         #
  1066.         for my $profile (sort keys %sd) {
  1067.             if (is_repo_profile($sd{$profile}{$profile})) {
  1068.                 push @repo_profiles, $profile;
  1069.             }
  1070.             if ( grep(/^$profile$/, @created) )  {
  1071.                 my $p_local = serialize_profile($sd{$profile},
  1072.                                                 $profile,
  1073.                                                 $serialize_opts);
  1074.                 if ( not defined $users_repo_profiles->{$profile} ) {
  1075.                     push @new_profiles,  [ $profile, $p_local, "" ];
  1076.                 } else {
  1077.                     my $p_repo = $users_repo_profiles->{$profile}->{profile};
  1078.                     if ( $p_local ne $p_repo ) {
  1079.                         push @changed_profiles, [ $profile, $p_local, $p_repo ];
  1080.                     }
  1081.                 }
  1082.             }
  1083.         }
  1084.  
  1085.         #
  1086.         # Find changes made to local profiles with repo metadata
  1087.         #
  1088.         if (@repo_profiles) {
  1089.             for my $profile (@repo_profiles) {
  1090.                 my $p_local = serialize_profile($sd{$profile},
  1091.                                                 $profile,
  1092.                                                 $serialize_opts);
  1093.                 if ( not exists $users_repo_profiles->{$profile} ) {
  1094.                     push @new_profiles,  [ $profile, $p_local, "" ];
  1095.                 } else {
  1096.                     my $p_repo = "";
  1097.                     if ( $sd{$profile}{$profile}{repo}{user} eq $user ) {
  1098.                        $p_repo = $users_repo_profiles->{$profile}->{profile};
  1099.                     }  else {
  1100.                         my ($status_ok,$ret) =
  1101.                             fetch_profile_by_id($cfg->{repository}{url},
  1102.                                                 $sd{$profile}{$profile}{repo}{id}
  1103.                                                );
  1104.                         if ( $status_ok ) {
  1105.                            $p_repo = $ret->{profile};
  1106.                         } else {
  1107.                             my $errmsg =
  1108.                               sprintf(
  1109.                                 gettext("WARNING: Error syncronizing profiles with the repository:\n%s\n"),
  1110.                                 $ret?$ret:gettext("UNKNOWN ERROR"));
  1111.                             UI_Important($errmsg);
  1112.                             next;
  1113.                         }
  1114.                     }
  1115.                     if ( $p_repo ne $p_local ) {
  1116.                         push @changed_profiles, [ $profile, $p_local, $p_repo ];
  1117.                     }
  1118.                 }
  1119.             }
  1120.         }
  1121.  
  1122.         if ( @changed_profiles ) {
  1123.            submit_changed_profiles( \@changed_profiles );
  1124.         }
  1125.         if ( @new_profiles ) {
  1126.            submit_created_profiles( \@new_profiles );
  1127.         }
  1128.     }
  1129. }
  1130.  
  1131. sub submit_created_profiles {
  1132.     my $new_profiles = shift;
  1133.     my $url = $cfg->{repository}{url};
  1134.  
  1135.     if ($UI_Mode eq "yast") {
  1136.         my $title       = gettext("New profiles");
  1137.         my $explanation =
  1138.           gettext("Please choose the newly created profiles that you would".
  1139.           " like\nto store in the repository");
  1140.         yast_select_and_upload_profiles($title,
  1141.                                         $explanation,
  1142.                                         $new_profiles);
  1143.     } else {
  1144.         my $title       =
  1145.           gettext("Submit newly created profiles to the repository");
  1146.         my $explanation =
  1147.           gettext("Would you like to upload the newly created profiles?");
  1148.         console_select_and_upload_profiles($title,
  1149.                                            $explanation,
  1150.                                            $new_profiles);
  1151.     }
  1152. }
  1153.  
  1154. sub submit_changed_profiles {
  1155.     my $changed_profiles = shift;
  1156.     my $url = $cfg->{repository}{url};
  1157.     if (@$changed_profiles) {
  1158.         if ($UI_Mode eq "yast") {
  1159.             my $explanation =
  1160.               gettext("Select which of the changed profiles you would".
  1161.               " like to upload\nto the repository");
  1162.             my $title       = gettext("Changed profiles");
  1163.             yast_select_and_upload_profiles($title,
  1164.                                             $explanation,
  1165.                                             $changed_profiles);
  1166.         } else {
  1167.             my $title       =
  1168.               gettext("Submit changed profiles to the repository");
  1169.             my $explanation =
  1170.               gettext("The following profiles from the repository were".
  1171.               " changed.\nWould you like to upload your changes?");
  1172.             console_select_and_upload_profiles($title,
  1173.                                                $explanation,
  1174.                                                $changed_profiles);
  1175.         }
  1176.     }
  1177. }
  1178.  
  1179. sub yast_select_and_upload_profiles {
  1180.  
  1181.     my ($title, $explanation, $profiles_ref) = @_;
  1182.     my $url = $cfg->{repository}{url};
  1183.     my %profile_changes;
  1184.     my @profiles = @$profiles_ref;
  1185.  
  1186.     foreach my $prof (@profiles) {
  1187.         $profile_changes{ $prof->[0] } =
  1188.           get_profile_diff($prof->[2], $prof->[1]);
  1189.     }
  1190.  
  1191.     my (@selected_profiles, $changelog, $changelogs, $single_changelog);
  1192.     SendDataToYast(
  1193.         {
  1194.             type               => "dialog-select-profiles",
  1195.             title              => $title,
  1196.             explanation        => $explanation,
  1197.             default_select     => "false",
  1198.             disable_ask_upload => "true",
  1199.             profiles           => \%profile_changes
  1200.         }
  1201.     );
  1202.     my ($ypath, $yarg) = GetDataFromYast();
  1203.     if ($yarg->{STATUS} eq "cancel") {
  1204.         return;
  1205.     } else {
  1206.         my $selected_profiles_ref = $yarg->{PROFILES};
  1207.         @selected_profiles = @$selected_profiles_ref;
  1208.         $changelogs        = $yarg->{CHANGELOG};
  1209.         if (defined $changelogs->{SINGLE_CHANGELOG}) {
  1210.             $changelog        = $changelogs->{SINGLE_CHANGELOG};
  1211.             $single_changelog = 1;
  1212.         }
  1213.     }
  1214.  
  1215.     for my $profile (@selected_profiles) {
  1216.         my ($user, $pass) = get_repo_user_pass();
  1217.         my $profile_string = serialize_profile($sd{$profile}, $profile);
  1218.         if (!$single_changelog) {
  1219.             $changelog = $changelogs->{$profile};
  1220.         }
  1221.         my ($status_ok, $ret) = upload_profile( $url,
  1222.                                                 $user,
  1223.                                                 $pass,
  1224.                                                 $cfg->{repository}{distro},
  1225.                                                 $profile,
  1226.                                                 $profile_string,
  1227.                                                 $changelog
  1228.                                               );
  1229.         if ($status_ok) {
  1230.             my $newprofile = $ret;
  1231.             my $newid      = $newprofile->{id};
  1232.             set_repo_info($sd{$profile}{$profile}, $url, $user, $newid);
  1233.             writeprofile_ui_feedback($profile);
  1234.         } else {
  1235.             my $errmsg =
  1236.               sprintf(
  1237.                 gettext("WARNING: An error occured while uploading the profile %s\n%s\n"),
  1238.                 $profile, $ret?$ret:gettext("UNKNOWN ERROR"));
  1239.             UI_Important( $errmsg );
  1240.         }
  1241.     }
  1242.     UI_Info(gettext("Uploaded changes to repository."));
  1243.  
  1244.     # Check to see if unselected profiles should be marked as local only
  1245.     # this is outside of the main repo code as we want users to be able to mark
  1246.     # profiles as local only even if they aren't able to connect to the repo.
  1247.     if (defined $yarg->{NEVER_ASK_AGAIN}) {
  1248.         my @unselected_profiles;
  1249.         foreach my $prof (@profiles) {
  1250.             if ( grep(/^$prof->[0]$/, @selected_profiles) == 0 ) {
  1251.                 push @unselected_profiles, $prof->[0];
  1252.             }
  1253.         }
  1254.         set_profiles_local_only( @unselected_profiles );
  1255.     }
  1256. }
  1257.  
  1258. sub console_select_and_upload_profiles {
  1259.     my ($title, $explanation, $profiles_ref) = @_;
  1260.     my $url = $cfg->{repository}{url};
  1261.     my @profiles = @$profiles_ref;
  1262.     my $q = {};
  1263.     $q->{title} = $title;
  1264.     $q->{headers} = [ "Repository", $url, ];
  1265.  
  1266.     $q->{explanation} = $explanation;
  1267.  
  1268.     $q->{functions} = [ "CMD_UPLOAD_CHANGES",
  1269.                         "CMD_VIEW_CHANGES",
  1270.                         "CMD_ASK_LATER",
  1271.                         "CMD_ASK_NEVER",
  1272.                         "CMD_ABORT", ];
  1273.  
  1274.     $q->{default} = "CMD_VIEW_CHANGES";
  1275.  
  1276.     $q->{options} = [ map { $_->[0] } @profiles ];
  1277.     $q->{selected} = 0;
  1278.  
  1279.     my ($ans, $arg);
  1280.     do {
  1281.         ($ans, $arg) = UI_PromptUser($q);
  1282.  
  1283.         if ($ans eq "CMD_VIEW_CHANGES") {
  1284.             display_changes($profiles[$arg]->[2], $profiles[$arg]->[1]);
  1285.         }
  1286.     } until $ans =~ /^CMD_(UPLOAD_CHANGES|ASK_NEVER|ASK_LATER)/;
  1287.  
  1288.     if ($ans eq "CMD_ASK_NEVER") {
  1289.         set_profiles_local_only(  map { $_->[0] } @profiles  );
  1290.     } elsif ($ans eq "CMD_UPLOAD_CHANGES") {
  1291.         my $changelog = UI_GetString(gettext("Changelog Entry: "), "");
  1292.         my ($user, $pass) = get_repo_user_pass();
  1293.         if ($user && $pass) {
  1294.             for my $p_data (@profiles) {
  1295.                 my $profile          = $p_data->[0];
  1296.                 my $profile_string   = $p_data->[1];
  1297.                 my ($status_ok,$ret) =
  1298.                     upload_profile( $url,
  1299.                                     $user,
  1300.                                     $pass,
  1301.                                     $cfg->{repository}{distro},
  1302.                                     $profile,
  1303.                                     $profile_string,
  1304.                                     $changelog
  1305.                                   );
  1306.                 if ($status_ok) {
  1307.                     my $newprofile = $ret;
  1308.                     my $newid      = $newprofile->{id};
  1309.                     set_repo_info($sd{$profile}{$profile}, $url, $user, $newid);
  1310.                     writeprofile_ui_feedback($profile);
  1311.                     UI_Info(
  1312.                       sprintf(gettext("Uploaded %s to repository."), $profile)
  1313.                     );
  1314.                 } else {
  1315.                     my $errmsg =
  1316.                       sprintf(
  1317.                         gettext("WARNING: An error occured while uploading the profile %s\n%s\n"),
  1318.                         $profile, $ret?$ret:gettext("UNKNOWN ERROR"));
  1319.                     UI_Important( $errmsg );
  1320.                 }
  1321.             }
  1322.         } else {
  1323.             UI_Important(gettext("Repository Error\n" .
  1324.                       "Registration or Signin was unsuccessful. User login\n" .
  1325.                       "information is required to upload profiles to the\n" .
  1326.                       "repository. These changes have not been sent.\n"));
  1327.         }
  1328.     }
  1329. }
  1330.  
  1331. #
  1332. # Mark the profiles passed in @profiles as local only
  1333. # and don't prompt to upload changes to the repository
  1334. #
  1335. sub set_profiles_local_only {
  1336.     my @profiles = @_;
  1337.     for my $profile (@profiles) {
  1338.          $sd{$profile}{$profile}{repo}{neversubmit} = 1;
  1339.          writeprofile_ui_feedback($profile);
  1340.     }
  1341. }
  1342.  
  1343. ##########################################################################
  1344. # Here are the console/yast interface functions
  1345.  
  1346. sub UI_Info ($) {
  1347.     my $text = shift;
  1348.  
  1349.     $DEBUGGING && debug "UI_Info: $UI_Mode: $text";
  1350.  
  1351.     if ($UI_Mode eq "text") {
  1352.         print "$text\n";
  1353.     } else {
  1354.         ycp::y2milestone($text);
  1355.     }
  1356. }
  1357.  
  1358. sub UI_Important ($) {
  1359.     my $text = shift;
  1360.  
  1361.     $DEBUGGING && debug "UI_Important: $UI_Mode: $text";
  1362.  
  1363.     if ($UI_Mode eq "text") {
  1364.         print "\n$text\n";
  1365.     } else {
  1366.         SendDataToYast({ type => "dialog-error", message => $text });
  1367.         my ($path, $yarg) = GetDataFromYast();
  1368.     }
  1369. }
  1370.  
  1371. sub UI_YesNo ($$) {
  1372.     my $text    = shift;
  1373.     my $default = shift;
  1374.  
  1375.     $DEBUGGING && debug "UI_YesNo: $UI_Mode: $text $default";
  1376.  
  1377.     my $ans;
  1378.     if ($UI_Mode eq "text") {
  1379.  
  1380.         my $yes = gettext("(Y)es");
  1381.         my $no  = gettext("(N)o");
  1382.  
  1383.         # figure out our localized hotkeys
  1384.         my $usrmsg = "PromptUser: " . gettext("Invalid hotkey for");
  1385.         $yes =~ /\((\S)\)/ or fatal_error "$usrmsg '$yes'";
  1386.         my $yeskey = lc($1);
  1387.         $no =~ /\((\S)\)/ or fatal_error "$usrmsg '$no'";
  1388.         my $nokey = lc($1);
  1389.  
  1390.         print "\n$text\n";
  1391.         if ($default eq "y") {
  1392.             print "\n[$yes] / $no\n";
  1393.         } else {
  1394.             print "\n$yes / [$no]\n";
  1395.         }
  1396.         $ans = getkey() || (($default eq "y") ? $yeskey : $nokey);
  1397.  
  1398.         # convert back from a localized answer to english y or n
  1399.         $ans = (lc($ans) eq $yeskey) ? "y" : "n";
  1400.     } else {
  1401.  
  1402.         SendDataToYast({ type => "dialog-yesno", question => $text });
  1403.         my ($ypath, $yarg) = GetDataFromYast();
  1404.         $ans = $yarg->{answer} || $default;
  1405.  
  1406.     }
  1407.  
  1408.     return $ans;
  1409. }
  1410.  
  1411. sub UI_YesNoCancel ($$) {
  1412.     my $text    = shift;
  1413.     my $default = shift;
  1414.  
  1415.     $DEBUGGING && debug "UI_YesNoCancel: $UI_Mode: $text $default";
  1416.  
  1417.     my $ans;
  1418.     if ($UI_Mode eq "text") {
  1419.  
  1420.         my $yes    = gettext("(Y)es");
  1421.         my $no     = gettext("(N)o");
  1422.         my $cancel = gettext("(C)ancel");
  1423.  
  1424.         # figure out our localized hotkeys
  1425.         my $usrmsg = "PromptUser: " . gettext("Invalid hotkey for");
  1426.         $yes =~ /\((\S)\)/ or fatal_error "$usrmsg '$yes'";
  1427.         my $yeskey = lc($1);
  1428.         $no =~ /\((\S)\)/ or fatal_error "$usrmsg '$no'";
  1429.         my $nokey = lc($1);
  1430.         $cancel =~ /\((\S)\)/ or fatal_error "$usrmsg '$cancel'";
  1431.         my $cancelkey = lc($1);
  1432.  
  1433.         $ans = "XXXINVALIDXXX";
  1434.         while ($ans !~ /^(y|n|c)$/) {
  1435.             print "\n$text\n";
  1436.             if ($default eq "y") {
  1437.                 print "\n[$yes] / $no / $cancel\n";
  1438.             } elsif ($default eq "n") {
  1439.                 print "\n$yes / [$no] / $cancel\n";
  1440.             } else {
  1441.                 print "\n$yes / $no / [$cancel]\n";
  1442.             }
  1443.  
  1444.             $ans = getkey();
  1445.  
  1446.             if ($ans) {
  1447.                 # convert back from a localized answer to english y or n
  1448.                 $ans = lc($ans);
  1449.                 if ($ans eq $yeskey) {
  1450.                     $ans = "y";
  1451.                 } elsif ($ans eq $nokey) {
  1452.                     $ans = "n";
  1453.                 } elsif ($ans eq $cancelkey) {
  1454.                     $ans = "c";
  1455.                 }
  1456.             } else {
  1457.                 $ans = $default;
  1458.             }
  1459.         }
  1460.     } else {
  1461.  
  1462.         SendDataToYast({ type => "dialog-yesnocancel", question => $text });
  1463.         my ($ypath, $yarg) = GetDataFromYast();
  1464.         $ans = $yarg->{answer} || $default;
  1465.  
  1466.     }
  1467.  
  1468.     return $ans;
  1469. }
  1470.  
  1471. sub UI_GetString ($$) {
  1472.     my $text    = shift;
  1473.     my $default = shift;
  1474.  
  1475.     $DEBUGGING && debug "UI_GetString: $UI_Mode: $text $default";
  1476.  
  1477.     my $string;
  1478.     if ($UI_Mode eq "text") {
  1479.  
  1480.         if ($term) {
  1481.             $string = $term->readline($text, $default);
  1482.         } else {
  1483.             local $| = 1;
  1484.             print "$text";
  1485.             $string = <STDIN>;
  1486.             chomp($string);
  1487.         }
  1488.  
  1489.     } else {
  1490.  
  1491.         SendDataToYast({
  1492.             type    => "dialog-getstring",
  1493.             label   => $text,
  1494.             default => $default
  1495.         });
  1496.         my ($ypath, $yarg) = GetDataFromYast();
  1497.         $string = $yarg->{string};
  1498.  
  1499.     }
  1500.     return $string;
  1501. }
  1502.  
  1503. sub UI_GetFile ($) {
  1504.     my $f = shift;
  1505.  
  1506.     $DEBUGGING && debug "UI_GetFile: $UI_Mode";
  1507.  
  1508.     my $filename;
  1509.     if ($UI_Mode eq "text") {
  1510.  
  1511.         local $| = 1;
  1512.         print "$f->{description}\n";
  1513.         $filename = <STDIN>;
  1514.         chomp($filename);
  1515.  
  1516.     } else {
  1517.  
  1518.         $f->{type} = "dialog-getfile";
  1519.  
  1520.         SendDataToYast($f);
  1521.         my ($ypath, $yarg) = GetDataFromYast();
  1522.         if ($yarg->{answer} eq "okay") {
  1523.             $filename = $yarg->{filename};
  1524.         }
  1525.     }
  1526.  
  1527.     return $filename;
  1528. }
  1529.  
  1530. sub UI_BusyStart ($) {
  1531.     my $message = shift;
  1532.     $DEBUGGING && debug "UI_BusyStart: $UI_Mode";
  1533.  
  1534.     if ($UI_Mode eq "text") {
  1535.       UI_Info( $message );
  1536.     } else {
  1537.         SendDataToYast({
  1538.                         type    => "dialog-busy-start",
  1539.                         message => $message,
  1540.                        });
  1541.         my ($ypath, $yarg) = GetDataFromYast();
  1542.     }
  1543. }
  1544.  
  1545. sub UI_BusyStop  {
  1546.     $DEBUGGING && debug "UI_BusyStop: $UI_Mode";
  1547.  
  1548.     if ($UI_Mode ne "text") {
  1549.         SendDataToYast({ type    => "dialog-busy-stop" });
  1550.         my ($ypath, $yarg) = GetDataFromYast();
  1551.     }
  1552. }
  1553.  
  1554.  
  1555. my %CMDS = (
  1556.     CMD_ALLOW            => "(A)llow",
  1557.     CMD_OTHER         => "(M)ore",
  1558.     CMD_AUDIT_NEW     => "Audi(t)",
  1559.     CMD_AUDIT_OFF     => "Audi(t) off",
  1560.     CMD_AUDIT_FULL     => "Audit (A)ll",
  1561.     CMD_OTHER         => "(O)pts",
  1562.     CMD_USER_ON         => "(O)wner permissions on",
  1563.     CMD_USER_OFF     => "(O)wner permissions off",
  1564.     CMD_DENY             => "(D)eny",
  1565.     CMD_ABORT            => "Abo(r)t",
  1566.     CMD_FINISHED         => "(F)inish",
  1567.     CMD_ix               => "(I)nherit",
  1568.     CMD_px               => "(P)rofile",
  1569.     CMD_px_safe         => "(P)rofile Clean Exec",
  1570.     CMD_cx         => "(C)hild",
  1571.     CMD_cx_safe         => "(C)hild Clean Exec",
  1572.     CMD_nx         => "(N)ame",
  1573.     CMD_nx_safe         => "(N)amed Clean Exec",
  1574.     CMD_ux               => "(U)nconfined",
  1575.     CMD_ux_safe         => "(U)nconfined Clean Exec",
  1576.     CMD_pix         => "(P)rofile ix",
  1577.     CMD_pix_safe     => "(P)rofile ix Clean Exec",
  1578.     CMD_cix         => "(C)hild ix",
  1579.     CMD_cix_safe     => "(C)hild ix Cx Clean Exec",
  1580.     CMD_nix         => "(N)ame ix",
  1581.     CMD_nix_safe     => "(N)ame ix",
  1582.     CMD_EXEC_IX_ON     => "(X)ix",
  1583.     CMD_EXEC_IX_OFF     => "(X)ix",
  1584.     CMD_SAVE             => "(S)ave Changes",
  1585.     CMD_CONTINUE         => "(C)ontinue Profiling",
  1586.     CMD_NEW              => "(N)ew",
  1587.     CMD_GLOB             => "(G)lob",
  1588.     CMD_GLOBEXT          => "Glob w/(E)xt",
  1589.     CMD_ADDHAT           => "(A)dd Requested Hat",
  1590.     CMD_USEDEFAULT       => "(U)se Default Hat",
  1591.     CMD_SCAN             => "(S)can system log for SubDomain events",
  1592.     CMD_HELP             => "(H)elp",
  1593.     CMD_VIEW_PROFILE     => "(V)iew Profile",
  1594.     CMD_USE_PROFILE      => "(U)se Profile",
  1595.     CMD_CREATE_PROFILE   => "(C)reate New Profile",
  1596.     CMD_UPDATE_PROFILE   => "(U)pdate Profile",
  1597.     CMD_IGNORE_UPDATE    => "(I)gnore Update",
  1598.     CMD_SAVE_CHANGES     => "(S)ave Changes",
  1599.     CMD_UPLOAD_CHANGES   => "(U)pload Changes",
  1600.     CMD_VIEW_CHANGES     => "(V)iew Changes",
  1601.     CMD_VIEW             => "(V)iew",
  1602.     CMD_ENABLE_REPO      => "(E)nable Repository",
  1603.     CMD_DISABLE_REPO     => "(D)isable Repository",
  1604.     CMD_ASK_NEVER        => "(N)ever Ask Again",
  1605.     CMD_ASK_LATER        => "Ask Me (L)ater",
  1606.     CMD_YES              => "(Y)es",
  1607.     CMD_NO               => "(N)o",
  1608.     CMD_ALL_NET          => "Allow All (N)etwork",
  1609.     CMD_NET_FAMILY       => "Allow Network Fa(m)ily",
  1610.     CMD_OVERWRITE        => "(O)verwrite Profile",
  1611.     CMD_KEEP             => "(K)eep Profile",
  1612.     CMD_CONTINUE         => "(C)ontinue",
  1613. );
  1614.  
  1615. sub UI_PromptUser ($) {
  1616.     my $q = shift;
  1617.  
  1618.     my ($cmd, $arg);
  1619.     if ($UI_Mode eq "text") {
  1620.  
  1621.         ($cmd, $arg) = Text_PromptUser($q);
  1622.  
  1623.     } else {
  1624.  
  1625.         $q->{type} = "wizard";
  1626.  
  1627.         SendDataToYast($q);
  1628.         my ($ypath, $yarg) = GetDataFromYast();
  1629.  
  1630.         $cmd = $yarg->{selection} || "CMD_ABORT";
  1631.         $arg = $yarg->{selected};
  1632.     }
  1633.  
  1634.     if ($cmd eq "CMD_ABORT") {
  1635.         confirm_and_abort();
  1636.         $cmd = "XXXINVALIDXXX";
  1637.     } elsif ($cmd eq "CMD_FINISHED") {
  1638.         confirm_and_finish();
  1639.         $cmd = "XXXINVALIDXXX";
  1640.     }
  1641.  
  1642.     if (wantarray) {
  1643.         return ($cmd, $arg);
  1644.     } else {
  1645.         return $cmd;
  1646.     }
  1647. }
  1648.  
  1649.  
  1650. sub UI_ShortMessage {
  1651.     my ($headline, $message) = @_;
  1652.  
  1653.     SendDataToYast(
  1654.         {
  1655.             type     => "short-dialog-message",
  1656.             headline => $headline,
  1657.             message  => $message
  1658.         }
  1659.     );
  1660.     my ($ypath, $yarg) = GetDataFromYast();
  1661. }
  1662.  
  1663. sub UI_LongMessage {
  1664.     my ($headline, $message) = @_;
  1665.  
  1666.     $headline = "MISSING" if not defined $headline;
  1667.     $message  = "MISSING" if not defined $message;
  1668.  
  1669.     SendDataToYast(
  1670.         {
  1671.             type     => "long-dialog-message",
  1672.             headline => $headline,
  1673.             message  => $message
  1674.         }
  1675.     );
  1676.     my ($ypath, $yarg) = GetDataFromYast();
  1677. }
  1678.  
  1679. ##########################################################################
  1680. # here are the interface functions to send data back and forth between
  1681. # the yast frontend and the perl backend
  1682.  
  1683. # this is super ugly, but waits for the next ycp Read command and sends data
  1684. # back to the ycp front end.
  1685.  
  1686. sub SendDataToYast {
  1687.     my $data = shift;
  1688.  
  1689.     $DEBUGGING && debug "SendDataToYast: Waiting for YCP command";
  1690.  
  1691.     while (<STDIN>) {
  1692.         $DEBUGGING && debug "SendDataToYast: YCP: $_";
  1693.         my ($ycommand, $ypath, $yargument) = ycp::ParseCommand($_);
  1694.  
  1695.         if ($ycommand && $ycommand eq "Read") {
  1696.  
  1697.             if ($DEBUGGING) {
  1698.                 my $debugmsg = Data::Dumper->Dump([$data], [qw(*data)]);
  1699.                 debug "SendDataToYast: Sending--\n$debugmsg";
  1700.             }
  1701.  
  1702.             ycp::Return($data);
  1703.             return 1;
  1704.  
  1705.         } else {
  1706.  
  1707.             $DEBUGGING && debug "SendDataToYast: Expected 'Read' but got-- $_";
  1708.  
  1709.         }
  1710.     }
  1711.  
  1712.     # if we ever break out here, something's horribly wrong.
  1713.     fatal_error "SendDataToYast: didn't receive YCP command before connection died";
  1714. }
  1715.  
  1716. # this is super ugly, but waits for the next ycp Write command and grabs
  1717. # whatever the ycp front end gives us
  1718.  
  1719. sub GetDataFromYast {
  1720.  
  1721.     $DEBUGGING && debug "GetDataFromYast: Waiting for YCP command";
  1722.  
  1723.     while (<STDIN>) {
  1724.         $DEBUGGING && debug "GetDataFromYast: YCP: $_";
  1725.         my ($ycmd, $ypath, $yarg) = ycp::ParseCommand($_);
  1726.  
  1727.         if ($DEBUGGING) {
  1728.             my $debugmsg = Data::Dumper->Dump([$yarg], [qw(*data)]);
  1729.             debug "GetDataFromYast: Received--\n$debugmsg";
  1730.         }
  1731.  
  1732.         if ($ycmd && $ycmd eq "Write") {
  1733.  
  1734.             ycp::Return("true");
  1735.             return ($ypath, $yarg);
  1736.  
  1737.         } else {
  1738.             $DEBUGGING && debug "GetDataFromYast: Expected 'Write' but got-- $_";
  1739.         }
  1740.     }
  1741.  
  1742.     # if we ever break out here, something's horribly wrong.
  1743.     fatal_error "GetDataFromYast: didn't receive YCP command before connection died";
  1744. }
  1745.  
  1746. sub confirm_and_abort {
  1747.     my $ans = UI_YesNo(gettext("Are you sure you want to abandon this set of profile changes and exit?"), "n");
  1748.     if ($ans eq "y") {
  1749.         UI_Info(gettext("Abandoning all changes."));
  1750.         shutdown_yast();
  1751.         exit 0;
  1752.     }
  1753. }
  1754.  
  1755. sub confirm_and_finish {
  1756.     die "FINISHING\n";
  1757. }
  1758.  
  1759. sub build_x_functions($$$) {
  1760.     my ($default, $options, $exec_toggle) = @_;
  1761.     my @{list};
  1762.     if ($exec_toggle) {
  1763.     push @list, "CMD_ix" if $options =~ /i/;
  1764.     push @list, "CMD_pix" if $options =~ /p/ and $options =~ /i/;
  1765.     push @list, "CMD_cix" if $options =~ /c/ and $options =~ /i/;
  1766.     push @list, "CMD_nix" if $options =~ /n/ and $options =~ /i/;
  1767.     push @list, "CMD_ux" if $options =~ /u/;
  1768.     } else {
  1769.     push @list, "CMD_ix" if $options =~ /i/;
  1770.     push @list, "CMD_px" if $options =~ /p/;
  1771.     push @list, "CMD_cx" if $options =~ /c/;
  1772.     push @list, "CMD_nx" if $options =~ /n/;
  1773.     push @list, "CMD_ux" if $options =~ /u/;
  1774.     }
  1775.     if ($exec_toggle) {
  1776.     push @list, "CMD_EXEC_IX_OFF" if $options =~/p|c|n/;
  1777.     } else {
  1778.     push @list, "CMD_EXEC_IX_ON" if $options =~/p|c|n/;
  1779.     }
  1780.     push @list, "CMD_DENY", "CMD_ABORT", "CMD_FINISHED";
  1781.     return @list;
  1782. }
  1783.  
  1784. ##########################################################################
  1785. # this is the hideously ugly function that descends down the flow/event
  1786. # trees that we've generated by parsing the logfile
  1787.  
  1788. sub handlechildren {
  1789.     my $profile = shift;
  1790.     my $hat     = shift;
  1791.     my $root    = shift;
  1792.  
  1793.     my @entries = @$root;
  1794.     for my $entry (@entries) {
  1795.         fatal_error "$entry is not a ref" if not ref($entry);
  1796.  
  1797.         if (ref($entry->[0])) {
  1798.             handlechildren($profile, $hat, $entry);
  1799.         } else {
  1800.  
  1801.             my @entry = @$entry;
  1802.             my $type  = shift @entry;
  1803.  
  1804.             if ($type eq "fork") {
  1805.                 my ($pid, $p, $h) = @entry;
  1806.  
  1807.                 if (   ($p !~ /null(-complain)*-profile/)
  1808.                     && ($h !~ /null(-complain)*-profile/))
  1809.                 {
  1810.                     $profile = $p;
  1811.                     $hat     = $h;
  1812.                 }
  1813.  
  1814.         if ($hat) {
  1815.             $profilechanges{$pid} = $profile . "//" . $hat;
  1816.         } else {
  1817.             $profilechanges{$pid} = $profile;
  1818.         }
  1819.             } elsif ($type eq "unknown_hat") {
  1820.                 my ($pid, $p, $h, $sdmode, $uhat) = @entry;
  1821.  
  1822.                 if ($p !~ /null(-complain)*-profile/) {
  1823.                     $profile = $p;
  1824.                 }
  1825.  
  1826.                 if ($sd{$profile}{$uhat}) {
  1827.                     $hat = $uhat;
  1828.                     next;
  1829.                 }
  1830.  
  1831.                 my $new_p = update_repo_profile($sd{$profile}{$profile});
  1832.                 if ( $new_p and
  1833.                      UI_SelectUpdatedRepoProfile($profile, $new_p) and
  1834.                      $sd{$profile}{$uhat} ) {
  1835.                     $hat = $uhat;
  1836.                     next;
  1837.                 }
  1838.  
  1839.                 # figure out what our default hat for this application is.
  1840.                 my $defaulthat;
  1841.                 for my $hatglob (keys %{$cfg->{defaulthat}}) {
  1842.                     $defaulthat = $cfg->{defaulthat}{$hatglob}
  1843.                       if $profile =~ /$hatglob/;
  1844.                 }
  1845.                 # keep track of previous answers for this run...
  1846.                 my $context = $profile;
  1847.                 $context .= " -> ^$uhat";
  1848.                 my $ans = $transitions{$context} || "XXXINVALIDXXX";
  1849.  
  1850.                 while ($ans !~ /^CMD_(ADDHAT|USEDEFAULT|DENY)$/) {
  1851.                     my $q = {};
  1852.                     $q->{headers} = [];
  1853.                     push @{ $q->{headers} }, gettext("Profile"), $profile;
  1854.                     if ($defaulthat) {
  1855.                         push @{ $q->{headers} }, gettext("Default Hat"), $defaulthat;
  1856.                     }
  1857.                     push @{ $q->{headers} }, gettext("Requested Hat"), $uhat;
  1858.  
  1859.                     $q->{functions} = [];
  1860.                     push @{ $q->{functions} }, "CMD_ADDHAT";
  1861.                     push @{ $q->{functions} }, "CMD_USEDEFAULT" if $defaulthat;
  1862.                     push @{$q->{functions}}, "CMD_DENY", "CMD_ABORT",
  1863.                       "CMD_FINISHED";
  1864.  
  1865.                     $q->{default} = ($sdmode eq "PERMITTING") ? "CMD_ADDHAT" : "CMD_DENY";
  1866.  
  1867.                     $seenevents++;
  1868.  
  1869.                     $ans = UI_PromptUser($q);
  1870.  
  1871.                 }
  1872.                 $transitions{$context} = $ans;
  1873.  
  1874.                 if ($ans eq "CMD_ADDHAT") {
  1875.                     $hat = $uhat;
  1876.                     $sd{$profile}{$hat}{flags} = $sd{$profile}{$profile}{flags};
  1877.                 } elsif ($ans eq "CMD_USEDEFAULT") {
  1878.                     $hat = $defaulthat;
  1879.                 } elsif ($ans eq "CMD_DENY") {
  1880.                     return;
  1881.                 }
  1882.  
  1883.             } elsif ($type eq "capability") {
  1884.                my ($pid, $p, $h, $prog, $sdmode, $capability) = @entry;
  1885.  
  1886.                 if (   ($p !~ /null(-complain)*-profile/)
  1887.                     && ($h !~ /null(-complain)*-profile/))
  1888.                 {
  1889.                     $profile = $p;
  1890.                     $hat     = $h;
  1891.                 }
  1892.  
  1893.                 # print "$pid $profile $hat $prog $sdmode capability $capability\n";
  1894.  
  1895.                 next unless $profile && $hat;
  1896.  
  1897.                 $prelog{$sdmode}{$profile}{$hat}{capability}{$capability} = 1;
  1898.             } elsif (($type eq "path") || ($type eq "exec")) {
  1899.                 my ($pid, $p, $h, $prog, $sdmode, $mode, $detail, $to_name) = @entry;
  1900.  
  1901.         $mode = 0 unless ($mode);
  1902.  
  1903.                 if (   ($p !~ /null(-complain)*-profile/)
  1904.                     && ($h !~ /null(-complain)*-profile/))
  1905.                 {
  1906.                     $profile = $p;
  1907.                     $hat     = $h;
  1908.                 }
  1909.  
  1910.                 next unless $profile && $hat;
  1911.                 my $domainchange = ($type eq "exec") ? "change" : "nochange";
  1912.  
  1913.                 # escape special characters that show up in literal paths
  1914.                 $detail =~ s/(\[|\]|\+|\*|\{|\})/\\$1/g;
  1915.  
  1916.                 # we need to give the Execute dialog if they're requesting x
  1917.                 # access for something that's not a directory - we'll force
  1918.                 # a "ix" Path dialog for directories
  1919.                 my $do_execute  = 0;
  1920.                 my $exec_target = $detail;
  1921.  
  1922.                 if ($mode & str_to_mode("x")) {
  1923.                     if (-d $exec_target) {
  1924.             $mode &= (~$ALL_AA_EXEC_TYPE);
  1925.                         $mode |= str_to_mode("ix");
  1926.                     } else {
  1927.                         $do_execute = 1;
  1928.                     }
  1929.                 }
  1930.  
  1931.         if ($mode & $AA_MAY_LINK) {
  1932.                     if ($detail =~ m/^from (.+) to (.+)$/) {
  1933.                         my ($path, $target) = ($1, $2);
  1934.  
  1935.                         my $frommode = str_to_mode("lr");
  1936.                         if (defined $prelog{$sdmode}{$profile}{$hat}{path}{$path}) {
  1937.                             $frommode |= $prelog{$sdmode}{$profile}{$hat}{path}{$path};
  1938.                         }
  1939.                         $prelog{$sdmode}{$profile}{$hat}{path}{$path} = $frommode;
  1940.  
  1941.                         my $tomode = str_to_mode("lr");
  1942.                         if (defined $prelog{$sdmode}{$profile}{$hat}{path}{$target}) {
  1943.                             $tomode |= $prelog{$sdmode}{$profile}{$hat}{path}{$target};
  1944.                         }
  1945.                         $prelog{$sdmode}{$profile}{$hat}{path}{$target} = $tomode;
  1946.  
  1947.                         # print "$pid $profile $hat $prog $sdmode $path:$frommode -> $target:$tomode\n";
  1948.                     } else {
  1949.                         next;
  1950.                     }
  1951.                 } elsif ($mode) {
  1952.                     my $path = $detail;
  1953.  
  1954.                     if (defined $prelog{$sdmode}{$profile}{$hat}{path}{$path}) {
  1955.                         $mode |= $prelog{$sdmode}{$profile}{$hat}{path}{$path};
  1956.                     }
  1957.                     $prelog{$sdmode}{$profile}{$hat}{path}{$path} = $mode;
  1958.  
  1959.                     # print "$pid $profile $hat $prog $sdmode $mode $path\n";
  1960.                 }
  1961.  
  1962.                 if ($do_execute) {
  1963.                     next if ( profile_known_exec($sd{$profile}{$hat},
  1964.                          "exec", $exec_target ) );
  1965.  
  1966.                     my $p = update_repo_profile($sd{$profile}{$profile});
  1967.  
  1968.             if ($to_name) {
  1969.             next if ( $to_name and
  1970.                   UI_SelectUpdatedRepoProfile($profile, $p) and
  1971.                   profile_known_exec($sd{$profile}{$hat},
  1972.                              "exec", $to_name ) );
  1973.             } else {
  1974.             next if ( UI_SelectUpdatedRepoProfile($profile, $p) and
  1975.                   profile_known_exec($sd{$profile}{$hat},
  1976.                              "exec", $exec_target ) );
  1977.             }
  1978.  
  1979.                     my $context = $profile;
  1980.                     $context .= "^$hat" if $profile ne $hat;
  1981.                     $context .= " -> $exec_target";
  1982.                     my $ans = $transitions{$context} || "";
  1983.  
  1984.                     my ($combinedmode, $combinedaudit, $cm, $am, @m);
  1985.             $combinedmode = 0;
  1986.             $combinedaudit = 0;
  1987.  
  1988.                     # does path match any regexps in original profile?
  1989.                     ($cm, $am, @m) = rematchfrag($sd{$profile}{$hat}, 'allow', $exec_target);
  1990.                     $combinedmode |= $cm if $cm;
  1991.             $combinedaudit |= $am if $am;
  1992.  
  1993.             # find the named transition if is present
  1994.             if ($combinedmode & str_to_mode("x")) {
  1995.             my $nt_name;
  1996.             foreach my $entry (@m) {
  1997.                 if ($sd{$profile}{$hat}{allow}{path}{$entry}{to}) {
  1998.                 $nt_name = $sd{$profile}{$hat}{allow}{path}{$entry}{to};
  1999.                 last;
  2000.                 }
  2001.             }
  2002.             if ($to_name and $nt_name and ($to_name ne $nt_name)) {
  2003.                 #fatal_error "transition name from "
  2004.             } elsif ($nt_name) {
  2005.                 $to_name = $nt_name;
  2006.             }
  2007.             }
  2008.  
  2009.                     # does path match anything pulled in by includes in
  2010.                     # original profile?
  2011.                     ($cm, $am, @m) = match_prof_incs_to_path($sd{$profile}{$hat}, 'allow', $exec_target);
  2012.                     $combinedmode |= $cm if $cm;
  2013.                     $combinedaudit |= $am if $am;
  2014.             if ($combinedmode & str_to_mode("x")) {
  2015.             my $nt_name;
  2016.             foreach my $entry (@m) {
  2017.                 if ($sd{$profile}{$hat}{allow}{path}{$entry}{to}) {
  2018.                 $nt_name = $sd{$profile}{$hat}{allow}{path}{$entry}{to};
  2019.                 last;
  2020.                 }
  2021.             }
  2022.             if ($to_name and $nt_name and ($to_name ne $nt_name)) {
  2023.                 #fatal_error "transition name from "
  2024.             } elsif ($nt_name) {
  2025.                 $to_name = $nt_name;
  2026.             }
  2027.             }
  2028.  
  2029.  
  2030.             #nx does not exist in profiles.  It does in log
  2031.             #files however.  The log parsing routines will convert
  2032.             #it to its profile form.
  2033.             #nx is internally represented by cx/px/cix/pix + to_name
  2034.                     my $exec_mode = 0;
  2035.             if (contains($combinedmode, "pix")) {
  2036.             if ($to_name) {
  2037.                 $ans = "CMD_nix";
  2038.             } else {
  2039.                 $ans = "CMD_pix";
  2040.             }
  2041.             $exec_mode = str_to_mode("pixr");
  2042.             } elsif (contains($combinedmode, "cix")) {
  2043.             if ($to_name) {
  2044.                 $ans = "CMD_nix";
  2045.             } else {
  2046.                 $ans = "CMD_cix";
  2047.             }
  2048.             $exec_mode = str_to_mode("cixr");
  2049.             } elsif (contains($combinedmode, "Pix")) {
  2050.             if ($to_name) {
  2051.                 $ans = "CMD_nix_safe";
  2052.             } else {
  2053.                 $ans = "CMD_pix_safe";
  2054.             }
  2055.             $exec_mode = str_to_mode("Pixr");
  2056.             } elsif (contains($combinedmode, "Cix")) {
  2057.             if ($to_name) {
  2058.                 $ans = "CMD_nix_safe";
  2059.             } else {
  2060.                 $ans = "CMD_cix_safe";
  2061.             }
  2062.             $exec_mode = str_to_mode("Cixr");
  2063.             } elsif (contains($combinedmode, "ix")) {
  2064.                         $ans       = "CMD_ix";
  2065.                         $exec_mode = str_to_mode("ixr");
  2066.                     } elsif (contains($combinedmode, "px")) {
  2067.             if ($to_name) {
  2068.                 $ans = "CMD_nx";
  2069.             } else {
  2070.                 $ans = "CMD_px";
  2071.             }
  2072.                         $exec_mode = str_to_mode("px");
  2073.             } elsif (contains($combinedmode, "cx")) {
  2074.             if ($to_name) {
  2075.                 $ans = "CMD_nx";
  2076.             } else {
  2077.                 $ans = "CMD_cx";
  2078.             }
  2079.             $exec_mode = str_to_mode("cx");
  2080.                     } elsif (contains($combinedmode, "ux")) {
  2081.                         $ans       = "CMD_ux";
  2082.                         $exec_mode = str_to_mode("ux");
  2083.                     } elsif (contains($combinedmode, "Px")) {
  2084.             if ($to_name) {
  2085.                 $ans = "CMD_nx_safe";
  2086.             } else {
  2087.                 $ans       = "CMD_px_safe";
  2088.             }
  2089.                         $exec_mode = str_to_mode("Px");
  2090.             } elsif (contains($combinedmode, "Cx")) {
  2091.             if ($to_name) {
  2092.                 $ans = "CMD_nx_safe";
  2093.             } else {
  2094.                 $ans = "CMD_cx_safe";
  2095.             }
  2096.             $exec_mode = str_to_mode("Cx");
  2097.                     } elsif (contains($combinedmode, "Ux")) {
  2098.                         $ans       = "CMD_ux_safe";
  2099.                         $exec_mode = str_to_mode("Ux");
  2100.                     } else {
  2101.                         my $options = $cfg->{qualifiers}{$exec_target} || "ipcnu";
  2102.             fatal_error "$entry has transition name but not transition mode" if $to_name;
  2103.  
  2104.                         # force "ix" as the only option when the profiled
  2105.                         # program executes itself
  2106.                         $options = "i" if $exec_target eq $profile;
  2107.  
  2108.             # for now don't allow hats to cx
  2109.             $options =~ s/c// if $hat and $hat ne $profile;
  2110.  
  2111.                         # we always need deny...
  2112.                         $options .= "d";
  2113.  
  2114.                         # figure out what our default option should be...
  2115.                         my $default;
  2116.                         if ($options =~ /p/
  2117.                             && -e getprofilefilename($exec_target))
  2118.                         {
  2119.                             $default = "CMD_px";
  2120.                         } elsif ($options =~ /i/) {
  2121.                             $default = "CMD_ix";
  2122.                         } elsif ($options =~ /c/) {
  2123.                             $default = "CMD_cx";
  2124.                         } elsif ($options =~ /n/) {
  2125.                             $default = "CMD_nx";
  2126.                         } else {
  2127.                             $default = "CMD_DENY";
  2128.                         }
  2129.  
  2130.                         # ugh, this doesn't work if someone does an ix before
  2131.                         # calling this particular child process.  at least
  2132.                         # it's only a hint instead of mandatory to get this
  2133.                         # right.
  2134.                         my $parent_uses_ld_xxx = check_for_LD_XXX($profile);
  2135.  
  2136.                         my $severity = $sevdb->rank($exec_target, "x");
  2137.  
  2138.                         # build up the prompt...
  2139.                         my $q = {};
  2140.                         $q->{headers} = [];
  2141.                         push @{ $q->{headers} }, gettext("Profile"), combine_name($profile, $hat);
  2142.                         if ($prog && $prog ne "HINT") {
  2143.                             push @{ $q->{headers} }, gettext("Program"), $prog;
  2144.                         }
  2145.             # $to_name should NOT exist here other wise we know what
  2146.             # mode we are supposed to be transitioning to
  2147.             # which is handled above.
  2148.                         push @{ $q->{headers} }, gettext("Execute"),  $exec_target;
  2149.                         push @{ $q->{headers} }, gettext("Severity"), $severity;
  2150.  
  2151.                         $q->{functions} = [];
  2152.  
  2153.                         my $prompt = "\n$context\n";
  2154.             my $exec_toggle = 0;
  2155.  
  2156.             push @{ $q->{functions} }, build_x_functions($default, $options, $exec_toggle);
  2157.  
  2158.                         $options = join("|", split(//, $options));
  2159.  
  2160.                         $seenevents++;
  2161.  
  2162.             while ($ans !~ m/^CMD_(ix|px|cx|nx|pix|cix|nix|px_safe|cx_safe|nx_safe|pix_safe|cix_safe|nix_safe|ux|ux_safe|EXEC_TOGGLE|DENY)$/) {
  2163.                 $ans = UI_PromptUser($q);
  2164.  
  2165.                 if ($ans =~ /CMD_EXEC_IX_/) {
  2166.                 $exec_toggle = !$exec_toggle;
  2167.  
  2168.                 $q->{functions} = [ ];
  2169.                 push @{ $q->{functions} }, build_x_functions($default, $options, $exec_toggle);
  2170.                 $ans = "";
  2171.                 next;
  2172.                 }
  2173.                 if ($ans =~ /CMD_(nx|nix)/) {
  2174.                                 my $arg = $exec_target;
  2175.  
  2176.                 my $ynans = "n";
  2177.                 if ($profile eq $hat) {
  2178.                     $ynans = UI_YesNo("Are you specifying a transition to a local profile?", "n");
  2179.                 }
  2180.  
  2181.                 if ($ynans eq "y") {
  2182.                     if ($ans eq "CMD_nx") {
  2183.                     $ans = "CMD_cx";
  2184.                     } else {
  2185.                     $ans = "CMD_cix";
  2186.                     }
  2187.                 } else {
  2188.                     if ($ans eq "CMD_nx") {
  2189.                     $ans = "CMD_px";
  2190.                     } else {
  2191.                     $ans = "CMD_pix";
  2192.                     }
  2193.                 }
  2194.                 $to_name = UI_GetString(gettext("Enter profile name to transition to: "), $arg);
  2195.                 }
  2196.                 if ($ans =~ /CMD_ix/) {
  2197.                 $exec_mode = str_to_mode("ix");
  2198.                             } elsif ($ans =~ /CMD_(px|cx|nx|pix|cix|nix)/) {
  2199.                 my $match = $1;
  2200.                 $exec_mode = str_to_mode($match);
  2201.                                 my $px_default = "n";
  2202.                                 my $px_mesg    = gettext("Should AppArmor sanitize the environment when\nswitching profiles?\n\nSanitizing the environment is more secure,\nbut some applications depend on the presence\nof LD_PRELOAD or LD_LIBRARY_PATH.");
  2203.                                 if ($parent_uses_ld_xxx) {
  2204.                                     $px_mesg = gettext("Should AppArmor sanitize the environment when\nswitching profiles?\n\nSanitizing the environment is more secure,\nbut this application appears to use LD_PRELOAD\nor LD_LIBRARY_PATH and clearing these could\ncause functionality problems.");
  2205.                                 }
  2206.                                 my $ynans = UI_YesNo($px_mesg, $px_default);
  2207.                 $ans = "CMD_$match";
  2208.                                 if ($ynans eq "y") {
  2209.                                     $exec_mode &= ~$AA_EXEC_UNSAFE;
  2210.                                 }
  2211.                             } elsif ($ans eq "CMD_ux") {
  2212.                 $exec_mode = str_to_mode("ux");
  2213.                                 my $ynans = UI_YesNo(sprintf(gettext("Launching processes in an unconfined state is a very\ndangerous operation and can cause serious security holes.\n\nAre you absolutely certain you wish to remove all\nAppArmor protection when executing \%s?"), $exec_target), "n");
  2214.                                 if ($ynans eq "y") {
  2215.                                     my $ynans = UI_YesNo(gettext("Should AppArmor sanitize the environment when\nrunning this program unconfined?\n\nNot sanitizing the environment when unconfining\na program opens up significant security holes\nand should be avoided if at all possible."), "y");
  2216.                                     if ($ynans eq "y") {
  2217.                     $exec_mode &= ~($AA_EXEC_UNSAFE | ($AA_EXEC_UNSAFE << $AA_OTHER_SHIFT));
  2218.                                     }
  2219.                                 } else {
  2220.                                     $ans = "INVALID";
  2221.                                 }
  2222.                             }
  2223.                         }
  2224.                         $transitions{$context} = $ans;
  2225.  
  2226.             if ($ans =~ /CMD_(ix|px|cx|nx|pix|cix|nix)/) {
  2227.                 # if we're inheriting, things'll bitch unless we have r
  2228.                 if ($exec_mode & str_to_mode("i")) {
  2229.                 $exec_mode |= str_to_mode("r");
  2230.                 }
  2231.  
  2232.             } else {
  2233.                 if ($ans eq "CMD_DENY") {
  2234.                 $sd{$profile}{$hat}{deny}{path}{$exec_target}{mode} |= str_to_mode("x");
  2235.  
  2236.                 $sd{$profile}{$hat}{deny}{path}{$exec_target}{audit} |= 0;
  2237.                 $changed{$profile} = 1;
  2238.                 }
  2239.  
  2240.                             # skip all remaining events if they say to deny
  2241.                             # the exec
  2242.                             return if $domainchange eq "change";
  2243.                         }
  2244.  
  2245.             unless ($ans eq "CMD_DENY") {
  2246. # ???? if its defined in the prelog we shouldn't have asked
  2247.                             if (defined $prelog{PERMITTING}{$profile}{$hat}{path}{$exec_target}) {
  2248. #                                $exec_mode = $prelog{PERMITTING}{$profile}{$hat}{path}{$exec_target};
  2249.                             }
  2250.                             $prelog{PERMITTING}{$profile}{$hat}{path}{$exec_target} |= $exec_mode;
  2251.                             $log{PERMITTING}{$profile}              = {};
  2252.                             $sd{$profile}{$hat}{allow}{path}{$exec_target}{mode} = $exec_mode;
  2253.                             $sd{$profile}{$hat}{allow}{path}{$exec_target}{audit} |= 0;
  2254.                             $sd{$profile}{$hat}{allow}{path}{$exec_target}{to} = $to_name if ($to_name);
  2255.  
  2256.                             # mark this profile as changed
  2257.                             $changed{$profile} = 1;
  2258.  
  2259.                             if ($exec_mode & str_to_mode("i")) {
  2260.                                 if ($exec_target =~ /perl/) {
  2261.                                     $sd{$profile}{$hat}{include}{"abstractions/perl"} = 1;
  2262.                                 } elsif ($detail =~ m/\/bin\/(bash|sh)/) {
  2263.                                     $sd{$profile}{$hat}{include}{"abstractions/bash"} = 1;
  2264.                                 }
  2265.                                 my $hashbang = head($exec_target);
  2266.                                 if ($hashbang =~ /^#!\s*(\S+)/) {
  2267.                                     my $interpreter = get_full_path($1);
  2268.                                     $sd{$profile}{$hat}{path}->{$interpreter}{mode} = str_to_mode("ix");
  2269.                                     $sd{$profile}{$hat}{path}->{$interpreter}{audit} |= 0;
  2270.                                     if ($interpreter =~ /perl/) {
  2271.                                         $sd{$profile}{$hat}{include}{"abstractions/perl"} = 1;
  2272.                                     } elsif ($interpreter =~ m/\/bin\/(bash|sh)/) {
  2273.                                         $sd{$profile}{$hat}{include}{"abstractions/bash"} = 1;
  2274.                                     }
  2275.                                 }
  2276.                             }
  2277.                         }
  2278.             }
  2279.  
  2280.                     # print "$pid $profile $hat EXEC $exec_target $ans $exec_mode\n";
  2281.  
  2282.                     # update our tracking info based on what kind of change
  2283.                     # this is...
  2284.                     if ($ans eq "CMD_ix") {
  2285.             if ($hat) {
  2286.                 $profilechanges{$pid} = $profile . "//" . $hat;
  2287.             } else {
  2288.                 $profilechanges{$pid} = $profile;
  2289.             }
  2290.                     } elsif ($ans =~ /^CMD_(px|nx|pix|nix)/) {
  2291.             $exec_target = $to_name if ($to_name);
  2292.                         if ($sdmode eq "PERMITTING") {
  2293.                             if ($domainchange eq "change") {
  2294.                                 $profile              = $exec_target;
  2295.                                 $hat                  = $exec_target;
  2296.                                 $profilechanges{$pid} = $profile;
  2297.                             }
  2298.                         }
  2299.                         # if they want to use px, make sure a profile
  2300.                         # exists for the target.
  2301.                         unless (-e getprofilefilename($exec_target)) {
  2302.                 my $ynans = "y";
  2303.                 if ($exec_mode & str_to_mode("i")) {
  2304.                 $ynans = UI_YesNo(sprintf(gettext("A profile for %s does not exist create one?"), $exec_target), "n");
  2305.                 }
  2306.                 if ($ynans eq "y") {
  2307.                 $helpers{$exec_target} = "enforce";
  2308.                 if ($to_name) {
  2309.                     autodep_base("", $exec_target);
  2310.                 } else {
  2311.                     autodep_base($exec_target, "");
  2312.                 }
  2313.                 reload_base($exec_target);
  2314.                 }
  2315.                         }
  2316.                     } elsif ($ans =~ /^CMD_(cx|cix)/) {
  2317.             $exec_target = $to_name if ($to_name);
  2318.                         if ($sdmode eq "PERMITTING") {
  2319.                             if ($domainchange eq "change") {
  2320.                                 $profilechanges{$pid} = "${profile}//${exec_target}";
  2321. #                                $profile              = $exec_target;
  2322. #                                $hat                  = $exec_target;
  2323.                             }
  2324.                         }
  2325.  
  2326.                         # if they want to use cx, make sure a profile
  2327.                         # exists for the target.
  2328.             unless ($sd{$profile}{$exec_target}) {
  2329.                 my $ynans = "y";
  2330.                 if ($exec_mode & str_to_mode("i")) {
  2331.                 $ynans = UI_YesNo(sprintf(gettext("A local profile for %s does not exist create one?"), $exec_target), "n");
  2332.                 }
  2333.                 if ($ynans eq "y") {
  2334.                 $hat = $exec_target;
  2335.                 # keep track of profile flags
  2336.                 #$profile_data->{$profile}{$hat}{flags} = ;
  2337.  
  2338.                 # we have seen more than a declaration so clear it
  2339.                 $sd{$profile}{$hat}{'declared'} = 0;
  2340.                 $sd{$profile}{$hat}{profile} = 1;
  2341.                 $sd{$profile}{$hat}{allow}{path} = { };
  2342.                 $sd{$profile}{$hat}{allow}{netdomain} = { };
  2343.                 my $file = $sd{$profile}{$profile}{filename};
  2344.                 $filelist{$file}{profiles}{$profile}{$hat} = 1;
  2345.  
  2346.                 }
  2347.                         }
  2348.                     } elsif ($ans =~ /^CMD_ux/) {
  2349.                         $profilechanges{$pid} = "unconstrained";
  2350.                         return if $domainchange eq "change";
  2351.                     }
  2352.                 }
  2353.             } elsif ( $type eq "netdomain" ) {
  2354.                my ($pid, $p, $h, $prog, $sdmode, $family, $sock_type, $protocol) =
  2355.                   @entry;
  2356.  
  2357.                 if (   ($p !~ /null(-complain)*-profile/)
  2358.                     && ($h !~ /null(-complain)*-profile/))
  2359.                 {
  2360.                     $profile = $p;
  2361.                     $hat     = $h;
  2362.                 }
  2363.  
  2364.                 next unless $profile && $hat;
  2365.                 $prelog{$sdmode}
  2366.                        {$profile}
  2367.                        {$hat}
  2368.                        {netdomain}
  2369.                        {$family}
  2370.                        {$sock_type} = 1 unless ( !$family || !$sock_type );
  2371.  
  2372.             }
  2373.         }
  2374.     }
  2375. }
  2376.  
  2377. sub add_to_tree ($@) {
  2378.     my ($pid, $type, @event) = @_;
  2379.     if ( $DEBUGGING ) {
  2380.         my $eventmsg = Data::Dumper->Dump([@event], [qw(*event)]);
  2381.         $eventmsg =~ s/\n/ /g;
  2382.         debug " add_to_tree: pid [$pid] type [$type] event [ $eventmsg ]";
  2383.     }
  2384.  
  2385.     unless (exists $pid{$pid}) {
  2386.         my $arrayref = [];
  2387.         push @log, $arrayref;
  2388.         $pid{$pid} = $arrayref;
  2389.     }
  2390.  
  2391.     push @{ $pid{$pid} }, [ $type, $pid, @event ];
  2392. }
  2393.  
  2394. #
  2395. # variables used in the logparsing routines
  2396. #
  2397. our $LOG;
  2398. our $next_log_entry;
  2399. our $logmark;
  2400. our $seenmark;
  2401. my $RE_LOG_v2_0_syslog = qr/SubDomain/;
  2402. my $RE_LOG_v2_1_syslog = qr/kernel:\s+(\[[\d\.\s]+\]\s+)?(audit\([\d\.\:]+\):\s+)?type=150[1-6]/;
  2403. my $RE_LOG_v2_0_audit  =
  2404.     qr/type=(APPARMOR|UNKNOWN\[1500\]) msg=audit\([\d\.\:]+\):/;
  2405. my $RE_LOG_v2_1_audit  =
  2406.     qr/type=(UNKNOWN\[150[1-6]\]|APPARMOR_(AUDIT|ALLOWED|DENIED|HINT|STATUS|ERROR))/;
  2407.  
  2408. sub prefetch_next_log_entry {
  2409.     # if we already have an existing cache entry, something's broken
  2410.     if ($next_log_entry) {
  2411.         print STDERR "Already had next log entry: $next_log_entry";
  2412.     }
  2413.  
  2414.     # read log entries until we either hit the end or run into an
  2415.     # AA event message format we recognize
  2416.     do {
  2417.         $next_log_entry = <$LOG>;
  2418.         $DEBUGGING && debug "prefetch_next_log_entry: next_log_entry = " . ($next_log_entry ? $next_log_entry : "empty");
  2419.     } until (!$next_log_entry || $next_log_entry =~ m{
  2420.         $RE_LOG_v2_0_syslog |
  2421.         $RE_LOG_v2_0_audit  |
  2422.         $RE_LOG_v2_1_audit  |
  2423.         $RE_LOG_v2_1_syslog |
  2424.         $logmark
  2425.     }x);
  2426. }
  2427.  
  2428. sub get_next_log_entry {
  2429.     # make sure we've got a next log entry if there is one
  2430.     prefetch_next_log_entry() unless $next_log_entry;
  2431.  
  2432.     # save a copy of the next log entry...
  2433.     my $log_entry = $next_log_entry;
  2434.  
  2435.     # zero out our cache of the next log entry
  2436.     $next_log_entry = undef;
  2437.  
  2438.     return $log_entry;
  2439. }
  2440.  
  2441. sub peek_at_next_log_entry {
  2442.     # make sure we've got a next log entry if there is one
  2443.     prefetch_next_log_entry() unless $next_log_entry;
  2444.  
  2445.     # return a copy of the next log entry without pulling it out of the cache
  2446.     return $next_log_entry;
  2447. }
  2448.  
  2449. sub throw_away_next_log_entry {
  2450.     $next_log_entry = undef;
  2451. }
  2452.  
  2453. sub parse_log_record_v_2_0 ($@) {
  2454.     my ($record, $last) = @_;
  2455.     $DEBUGGING && debug "parse_log_record_v_2_0: $record";
  2456.  
  2457.     # What's this early out for?  As far as I can tell, parse_log_record_v_2_0
  2458.     # won't ever be called without something in $record
  2459.     return $last if ( ! $record );
  2460.  
  2461.     $_ = $record;
  2462.  
  2463.     if (s/(PERMITTING|REJECTING)-SYSLOGFIX/$1/) {
  2464.         s/%%/%/g;
  2465.     }
  2466.  
  2467.     if (m/LOGPROF-HINT unknown_hat (\S+) pid=(\d+) profile=(.+) active=(.+)/) {
  2468.         my ($uhat, $pid, $profile, $hat) = ($1, $2, $3, $4);
  2469.  
  2470.         $last = $&;
  2471.  
  2472.         # we want to ignore entries for profiles that don't exist
  2473.         # they're most likely broken entries or old entries for
  2474.         # deleted profiles
  2475.         return $&
  2476.           if ( ($profile ne 'null-complain-profile')
  2477.             && (!profile_exists($profile)));
  2478.  
  2479.         add_to_tree($pid, "unknown_hat", $profile, $hat,
  2480.                     "PERMITTING", $uhat);
  2481.     } elsif (m/LOGPROF-HINT (unknown_profile|missing_mandatory_profile) image=(.+) pid=(\d+) profile=(.+) active=(.+)/) {
  2482.         my ($image, $pid, $profile, $hat) = ($2, $3, $4, $5);
  2483.  
  2484.         return $& if $last =~ /PERMITTING x access to $image/;
  2485.         $last = $&;
  2486.  
  2487.         # we want to ignore entries for profiles that don't exist
  2488.         # they're most likely broken entries or old entries for
  2489.         # deleted profiles
  2490.         return $&
  2491.           if ( ($profile ne 'null-complain-profile')
  2492.             && (!profile_exists($profile)));
  2493.  
  2494.         add_to_tree($pid, "exec", $profile, $hat, "HINT", "PERMITTING", "x", $image);
  2495.  
  2496.     } elsif (m/(PERMITTING|REJECTING) (\S+) access (.+) \((.+)\((\d+)\) profile (.+) active (.+)\)/) {
  2497.         my ($sdmode, $mode, $detail, $prog, $pid, $profile, $hat) =
  2498.            ($1, $2, $3, $4, $5, $6, $7);
  2499.  
  2500.     if ($mode eq "link") {
  2501.         $mode = "l";
  2502.     }
  2503.         if (!validate_log_mode($mode)) {
  2504.             fatal_error(sprintf(gettext('Log contains unknown mode %s.'), $mode));
  2505.         }
  2506.  
  2507.         my $domainchange = "nochange";
  2508.         if ($mode =~ /x/) {
  2509.  
  2510.             # we need to try to check if we're doing a domain transition
  2511.             if ($sdmode eq "PERMITTING") {
  2512.                 my $following = peek_at_next_log_entry();
  2513.  
  2514.                 if ($following && ($following =~ m/changing_profile/)) {
  2515.                     $domainchange = "change";
  2516.                     throw_away_next_log_entry();
  2517.                 }
  2518.             }
  2519.         } else {
  2520.  
  2521.             # we want to ignore duplicates for things other than executes...
  2522.             return $& if $seen{$&};
  2523.             $seen{$&} = 1;
  2524.         }
  2525.  
  2526.         $last = $&;
  2527.  
  2528.         # we want to ignore entries for profiles that don't exist
  2529.         # they're most likely broken entries or old entries for
  2530.         # deleted profiles
  2531.         if (($profile ne 'null-complain-profile')
  2532.             && (!profile_exists($profile)))
  2533.         {
  2534.             return $&;
  2535.         }
  2536.  
  2537.         # currently no way to stick pipe mediation in a profile, ignore
  2538.         # any messages like this
  2539.         return $& if $detail =~ /to pipe:/;
  2540.  
  2541.         # strip out extra extended attribute info since we don't
  2542.         # currently have a way to specify it in the profile and
  2543.         # instead just need to provide the access to the base filename
  2544.         $detail =~ s/\s+extended attribute \S+//;
  2545.  
  2546.         # kerberos code checks to see if the krb5.conf file is world
  2547.         # writable in a stupid way so we'll ignore any w accesses to
  2548.         # krb5.conf
  2549.         return $& if (($detail eq "to /etc/krb5.conf") && contains($mode, "w"));
  2550.  
  2551.         # strip off the (deleted) tag that gets added if it's a
  2552.         # deleted file
  2553.         $detail =~ s/\s+\(deleted\)$//;
  2554.  
  2555.     #            next if (($detail =~ /to \/lib\/ld-/) && ($mode =~ /x/));
  2556.  
  2557.         $detail =~ s/^to\s+//;
  2558.  
  2559.         if ($domainchange eq "change") {
  2560.             add_to_tree($pid, "exec", $profile, $hat, $prog,
  2561.                         $sdmode, str_to_mode($mode), $detail);
  2562.         } else {
  2563.             add_to_tree($pid, "path", $profile, $hat, $prog,
  2564.                         $sdmode, str_to_mode($mode), $detail);
  2565.         }
  2566.  
  2567.     } elsif (m/(PERMITTING|REJECTING) (?:mk|rm)dir on (.+) \((.+)\((\d+)\) profile (.+) active (.+)\)/) {
  2568.         my ($sdmode, $path, $prog, $pid, $profile, $hat) =
  2569.            ($1, $2, $3, $4, $5, $6);
  2570.  
  2571.         # we want to ignore duplicates for things other than executes...
  2572.         return $& if $seen{$&}++;
  2573.  
  2574.         $last = $&;
  2575.  
  2576.         # we want to ignore entries for profiles that don't exist
  2577.         # they're most likely broken entries or old entries for
  2578.         # deleted profiles
  2579.         return $&
  2580.           if ( ($profile ne 'null-complain-profile')
  2581.             && (!profile_exists($profile)));
  2582.  
  2583.         add_to_tree($pid, "path", $profile, $hat, $prog, $sdmode,
  2584.                     "w", $path);
  2585.  
  2586.     } elsif (m/(PERMITTING|REJECTING) xattr (\S+) on (.+) \((.+)\((\d+)\) profile (.+) active (.+)\)/) {
  2587.         my ($sdmode, $xattr_op, $path, $prog, $pid, $profile, $hat) =
  2588.            ($1, $2, $3, $4, $5, $6, $7);
  2589.  
  2590.         # we want to ignore duplicates for things other than executes...
  2591.         return $& if $seen{$&}++;
  2592.  
  2593.         $last = $&;
  2594.  
  2595.         # we want to ignore entries for profiles that don't exist
  2596.         # they're most likely broken entries or old entries for
  2597.         # deleted profiles
  2598.         return $&
  2599.           if ( ($profile ne 'null-complain-profile')
  2600.             && (!profile_exists($profile)));
  2601.  
  2602.         my $xattrmode;
  2603.         if ($xattr_op eq "get" || $xattr_op eq "list") {
  2604.             $xattrmode = "r";
  2605.         } elsif ($xattr_op eq "set" || $xattr_op eq "remove") {
  2606.             $xattrmode = "w";
  2607.         }
  2608.  
  2609.         if ($xattrmode) {
  2610.             add_to_tree($pid, "path", $profile, $hat, $prog, $sdmode,
  2611.                         str_to_mode($xattrmode), $path);
  2612.         }
  2613.  
  2614.     } elsif (m/(PERMITTING|REJECTING) attribute \((.*?)\) change to (.+) \((.+)\((\d+)\) profile (.+) active (.+)\)/) {
  2615.         my ($sdmode, $change, $path, $prog, $pid, $profile, $hat) =
  2616.            ($1, $2, $3, $4, $5, $6, $7);
  2617.  
  2618.         # we want to ignore duplicates for things other than executes...
  2619.         return $& if $seen{$&};
  2620.         $seen{$&} = 1;
  2621.  
  2622.         $last = $&;
  2623.  
  2624.         # we want to ignore entries for profiles that don't exist
  2625.         # they're most likely broken entries or old entries for
  2626.         # deleted profiles
  2627.         return $&
  2628.           if ( ($profile ne 'null-complain-profile')
  2629.             && (!profile_exists($profile)));
  2630.  
  2631.         # kerberos code checks to see if the krb5.conf file is world
  2632.         # writable in a stupid way so we'll ignore any w accesses to
  2633.         # krb5.conf
  2634.         return $& if $path eq "/etc/krb5.conf";
  2635.  
  2636.         add_to_tree($pid, "path", $profile, $hat, $prog, $sdmode,
  2637.                     str_to_mode("w"), $path);
  2638.  
  2639.     } elsif (m/(PERMITTING|REJECTING) access to capability '(\S+)' \((.+)\((\d+)\) profile (.+) active (.+)\)/) {
  2640.         my ($sdmode, $capability, $prog, $pid, $profile, $hat) =
  2641.            ($1, $2, $3, $4, $5, $6);
  2642.  
  2643.         return $& if $seen{$&};
  2644.  
  2645.         $seen{$&} = 1;
  2646.         $last = $&;
  2647.  
  2648.         # we want to ignore entries for profiles that don't exist - they're
  2649.         # most likely broken entries or old entries for deleted profiles
  2650.         return $&
  2651.           if ( ($profile ne 'null-complain-profile')
  2652.             && (!profile_exists($profile)));
  2653.  
  2654.         add_to_tree($pid, "capability", $profile, $hat, $prog,
  2655.                     $sdmode, $capability);
  2656.  
  2657.     } elsif (m/Fork parent (\d+) child (\d+) profile (.+) active (.+)/
  2658.         || m/LOGPROF-HINT fork pid=(\d+) child=(\d+) profile=(.+) active=(.+)/
  2659.         || m/LOGPROF-HINT fork pid=(\d+) child=(\d+)/)
  2660.     {
  2661.         my ($parent, $child, $profile, $hat) = ($1, $2, $3, $4);
  2662.  
  2663.         $profile ||= "null-complain-profile";
  2664.         $hat     ||= "null-complain-profile";
  2665.  
  2666.         $last = $&;
  2667.  
  2668.         # we want to ignore entries for profiles that don't exist
  2669.         # they're  most likely broken entries or old entries for
  2670.         # deleted profiles
  2671.         return $&
  2672.           if ( ($profile ne 'null-complain-profile')
  2673.             && (!profile_exists($profile)));
  2674.  
  2675.         my $arrayref = [];
  2676.         if (exists $pid{$parent}) {
  2677.             push @{ $pid{$parent} }, $arrayref;
  2678.         } else {
  2679.             push @log, $arrayref;
  2680.         }
  2681.         $pid{$child} = $arrayref;
  2682.         push @{$arrayref}, [ "fork", $child, $profile, $hat ];
  2683.     } else {
  2684.         $DEBUGGING && debug "UNHANDLED: $_";
  2685.     }
  2686.     return $last;
  2687. }
  2688.  
  2689. sub parse_log_record ($) {
  2690.     my $record = shift;
  2691.     $DEBUGGING && debug "parse_log_record: $record";
  2692.     my $e = parse_event($record);
  2693.  
  2694.     return $e;
  2695. }
  2696.  
  2697.  
  2698. sub add_event_to_tree ($) {
  2699.     my $e = shift;
  2700.  
  2701.     my $sdmode = $e->{sdmode}?$e->{sdmode}:"UNKNOWN";
  2702.     if ( $e->{type} ) {
  2703.         if ( $e->{type} =~ /(UNKNOWN\[1501\]|APPARMOR_AUDIT|1501)/ ) {
  2704.             $sdmode = "AUDIT";
  2705.         } elsif ( $e->{type} =~ /(UNKNOWN\[1502\]|APPARMOR_ALLOWED|1502)/ ) {
  2706.             $sdmode = "PERMITTING";
  2707.         } elsif ( $e->{type} =~ /(UNKNOWN\[1503\]|APPARMOR_DENIED|1503)/ ) {
  2708.             $sdmode = "REJECTING";
  2709.         } elsif ( $e->{type} =~ /(UNKNOWN\[1504\]|APPARMOR_HINT|1504)/ ) {
  2710.             $sdmode = "HINT";
  2711.         } elsif ( $e->{type} =~ /(UNKNOWN\[1505\]|APPARMOR_STATUS|1505)/ ) {
  2712.             $sdmode = "STATUS";
  2713.         } elsif ( $e->{type} =~ /(UNKNOWN\[1506\]|APPARMOR_ERROR|1506)/ ) {
  2714.             $sdmode = "ERROR";
  2715.         } else {
  2716.             $sdmode = "UNKNOWN";
  2717.         }
  2718.     }
  2719.     return if ( $sdmode =~ /UNKNOWN|AUDIT|STATUS|ERROR/ );
  2720.     return if ($e->{operation} =~ /profile_set/);
  2721.  
  2722.     my ($profile, $hat);
  2723.     ($profile, $hat) = split /\/\//, $e->{profile};
  2724.     if ( $e->{operation} eq "change_hat" ) {
  2725.         ($profile, $hat) = split /\/\//, $e->{name};
  2726.     }
  2727.     $hat = $profile if ( !$hat );
  2728.     # TODO - refactor add_to_tree as prog is no longer supplied
  2729.     #        HINT is from previous format where prog was not
  2730.     #        consistently passed
  2731.     my $prog = "HINT";
  2732.  
  2733.     return if ($profile ne 'null-complain-profile' && !profile_exists($profile));
  2734.  
  2735.     if ($e->{operation} eq "exec") {
  2736.         if ( defined $e->{info} && $e->{info} eq "mandatory profile missing" ) {
  2737.             add_to_tree( $e->{pid},
  2738.                          "exec",
  2739.                          $profile,
  2740.                          $hat,
  2741.                          $sdmode,
  2742.                          "PERMITTING",
  2743.                          $e->{denied_mask},
  2744.                          $e->{name},
  2745.                          $e->{name2}
  2746.                        );
  2747.         }
  2748.     } elsif ($e->{operation} =~ m/file_/) {
  2749.         add_to_tree( $e->{pid},
  2750.                      "path",
  2751.                      $profile,
  2752.                      $hat,
  2753.                      $prog,
  2754.                      $sdmode,
  2755.                      $e->{denied_mask},
  2756.                      $e->{name},
  2757.              "",
  2758.                    );
  2759.     } elsif ($e->{operation} eq "capable") {
  2760.         add_to_tree( $e->{pid},
  2761.                      "capability",
  2762.                      $profile,
  2763.                      $hat,
  2764.                      $prog,
  2765.                      $sdmode,
  2766.                      $e->{name}
  2767.                    );
  2768.     } elsif ($e->{operation} =~  m/xattr/ ||
  2769.              $e->{operation} eq "setattr") {
  2770.         add_to_tree( $e->{pid},
  2771.                      "path",
  2772.                      $profile,
  2773.                      $hat,
  2774.                      $prog,
  2775.                      $sdmode,
  2776.                      $e->{denied_mask},
  2777.                      $e->{name},
  2778.              ""
  2779.                     );
  2780.     } elsif ($e->{operation} =~ m/inode_/) {
  2781.         my $is_domain_change = 0;
  2782.  
  2783.         if ($e->{operation}   eq "inode_permission" &&
  2784.             $e->{denied_mask} & $AA_MAY_EXEC                &&
  2785.             $sdmode           eq "PERMITTING") {
  2786.  
  2787.             my $following = peek_at_next_log_entry();
  2788.             if ($following) {
  2789.                 my $entry = parse_log_record($following);
  2790.                 if ($entry &&
  2791.                     $entry->{info} &&
  2792.                     $entry->{info} eq "set profile" ) {
  2793.  
  2794.                     $is_domain_change = 1;
  2795.                     throw_away_next_log_entry();
  2796.                 }
  2797.             }
  2798.         }
  2799.  
  2800.         if ($is_domain_change) {
  2801.             add_to_tree( $e->{pid},
  2802.                           "exec",
  2803.                           $profile,
  2804.                           $hat,
  2805.                           $prog,
  2806.                           $sdmode,
  2807.                           $e->{denied_mask},
  2808.                           $e->{name},
  2809.               $e->{name2}
  2810.                         );
  2811.         } else {
  2812.              add_to_tree( $e->{pid},
  2813.                           "path",
  2814.                           $profile,
  2815.                           $hat,
  2816.                           $prog,
  2817.                           $sdmode,
  2818.                           $e->{denied_mask},
  2819.                           $e->{name},
  2820.               ""
  2821.                         );
  2822.         }
  2823.     } elsif ($e->{operation} eq "sysctl") {
  2824.         add_to_tree( $e->{pid},
  2825.                      "path",
  2826.                      $profile,
  2827.                      $hat,
  2828.                      $prog,
  2829.                      $sdmode,
  2830.                      $e->{denied_mask},
  2831.                      $e->{name},
  2832.              ""
  2833.                    );
  2834.     } elsif ($e->{operation} eq "clone") {
  2835.         my ($parent, $child)  = ($e->{pid}, $e->{task});
  2836.         $profile ||= "null-complain-profile";
  2837.         $hat     ||= "null-complain-profile";
  2838.         my $arrayref = [];
  2839.         if (exists $pid{$e->{pid}}) {
  2840.             push @{ $pid{$parent} }, $arrayref;
  2841.         } else {
  2842.             push @log, $arrayref;
  2843.         }
  2844.         $pid{$child} = $arrayref;
  2845.         push @{$arrayref}, [ "fork", $child, $profile, $hat ];
  2846.     } elsif ($e->{operation} =~ m/socket_/) {
  2847.         add_to_tree( $e->{pid},
  2848.                      "netdomain",
  2849.                      $profile,
  2850.                      $hat,
  2851.                      $prog,
  2852.                      $sdmode,
  2853.                      $e->{family},
  2854.                      $e->{sock_type},
  2855.                      $e->{protocol},
  2856.                    );
  2857.     } elsif ($e->{operation} eq "change_hat") {
  2858.         add_to_tree($e->{pid}, "unknown_hat", $profile, $hat, $sdmode, $hat);
  2859.     } else {
  2860.         if ( $DEBUGGING ) {
  2861.             my $msg = Data::Dumper->Dump([$e], [qw(*event)]);
  2862.             debug "UNHANDLED: $msg";
  2863.         }
  2864.     }
  2865. }
  2866.  
  2867. sub read_log {
  2868.     $logmark = shift;
  2869.     $seenmark = $logmark ? 0 : 1;
  2870.     my $last;
  2871.     my $event_type;
  2872.  
  2873.     # okay, done loading the previous profiles, get on to the good stuff...
  2874.     open($LOG, $filename)
  2875.       or fatal_error "Can't read AppArmor logfile $filename: $!";
  2876.     while ($_ = get_next_log_entry()) {
  2877.         chomp;
  2878.  
  2879.     $DEBUGGING && debug "read_log: $_";
  2880.  
  2881.         $seenmark = 1 if /$logmark/;
  2882.  
  2883.     $DEBUGGING && debug "read_log: seenmark = $seenmark";
  2884.         next unless $seenmark;
  2885.  
  2886.         my $last_match = ""; # v_2_0 syslog record parsing requires
  2887.                              # the previous aa record in the mandatory profile
  2888.                              # case
  2889.         # all we care about is apparmor messages
  2890.         if (/$RE_LOG_v2_0_syslog/ || /$RE_LOG_v2_0_audit/) {
  2891.            $last_match = parse_log_record_v_2_0( $_, $last_match );
  2892.         } else {
  2893.             my $event = parse_log_record($_);
  2894.             add_event_to_tree($event) if ( $event );
  2895.         }
  2896.     }
  2897.     close($LOG);
  2898.     $logmark = "";
  2899. }
  2900.  
  2901.  
  2902. sub UI_SelectUpdatedRepoProfile ($$) {
  2903.  
  2904.     my ($profile, $p) = @_;
  2905.     my $distro        = $cfg->{repository}{distro};
  2906.     my $url           = $sd{$profile}{$profile}{repo}{url};
  2907.     my $user          = $sd{$profile}{$profile}{repo}{user};
  2908.     my $id            = $sd{$profile}{$profile}{repo}{id};
  2909.     my $updated       = 0;
  2910.  
  2911.     if ($p) {
  2912.         my $q = { };
  2913.         $q->{headers} = [
  2914.           "Profile", $profile,
  2915.           "User", $user,
  2916.           "Old Revision", $id,
  2917.           "New Revision", $p->{id},
  2918.         ];
  2919.         $q->{explanation} =
  2920.           gettext( "An updated version of this profile has been found in the profile repository.  Would you like to use it?");
  2921.         $q->{functions} = [
  2922.           "CMD_VIEW_CHANGES", "CMD_UPDATE_PROFILE", "CMD_IGNORE_UPDATE",
  2923.           "CMD_ABORT", "CMD_FINISHED"
  2924.         ];
  2925.  
  2926.         my $ans;
  2927.         do {
  2928.             $ans = UI_PromptUser($q);
  2929.  
  2930.             if ($ans eq "CMD_VIEW_CHANGES") {
  2931.                 my $oldprofile = serialize_profile($sd{$profile}, $profile);
  2932.                 my $newprofile = $p->{profile};
  2933.                 display_changes($oldprofile, $newprofile);
  2934.             }
  2935.         } until $ans =~ /^CMD_(UPDATE_PROFILE|IGNORE_UPDATE)/;
  2936.  
  2937.         if ($ans eq "CMD_UPDATE_PROFILE") {
  2938.             eval {
  2939.                 my $profile_data =
  2940.                   parse_profile_data($p->{profile}, getprofilefilename($profile), 0);
  2941.                 if ($profile_data) {
  2942.                     attach_profile_data(\%sd, $profile_data);
  2943.                     $changed{$profile} = 1;
  2944.                 }
  2945.  
  2946.                 set_repo_info($sd{$profile}{$profile}, $url, $user, $p->{id});
  2947.  
  2948.                 UI_Info(
  2949.                     sprintf(
  2950.                         gettext("Updated profile %s to revision %s."),
  2951.                         $profile, $p->{id}
  2952.                     )
  2953.                 );
  2954.             };
  2955.  
  2956.             if ($@) {
  2957.                 UI_Info(gettext("Error parsing repository profile."));
  2958.             } else {
  2959.                 $updated = 1;
  2960.             }
  2961.         }
  2962.     }
  2963.     return $updated;
  2964. }
  2965.  
  2966. sub UI_repo_signup {
  2967.  
  2968.     my ($url, $res, $save_config, $newuser, $user, $pass, $email, $signup_okay);
  2969.     $url = $cfg->{repository}{url};
  2970.     do {
  2971.         if ($UI_Mode eq "yast") {
  2972.             SendDataToYast(
  2973.                 {
  2974.                     type     => "dialog-repo-sign-in",
  2975.                     repo_url => $url
  2976.                 }
  2977.             );
  2978.             my ($ypath, $yarg) = GetDataFromYast();
  2979.             $email       = $yarg->{email};
  2980.             $user        = $yarg->{user};
  2981.             $pass        = $yarg->{pass};
  2982.             $newuser     = $yarg->{newuser};
  2983.             $save_config = $yarg->{save_config};
  2984.             if ($yarg->{cancelled} && $yarg->{cancelled} eq "y") {
  2985.                 return;
  2986.             }
  2987.             $DEBUGGING && debug("AppArmor Repository: \n\t " .
  2988.                                 ($newuser eq "1") ?
  2989.                                 "New User\n\temail: [" . $email . "]" :
  2990.                                 "Signin" . "\n\t user[" . $user . "]" .
  2991.                                 "password [" . $pass . "]\n");
  2992.         } else {
  2993.             $newuser = UI_YesNo(gettext("Create New User?"), "n");
  2994.             $user    = UI_GetString(gettext("Username: "), $user);
  2995.             $pass    = UI_GetString(gettext("Password: "), $pass);
  2996.             $email   = UI_GetString(gettext("Email Addr: "), $email)
  2997.                          if ($newuser eq "y");
  2998.             $save_config = UI_YesNo(gettext("Save Configuration? "), "y");
  2999.         }
  3000.  
  3001.         if ($newuser eq "y") {
  3002.             my ($status_ok,$res) = user_register($url, $user, $pass, $email);
  3003.             if ($status_ok) {
  3004.                 $signup_okay = 1;
  3005.             } else {
  3006.                 my $errmsg =
  3007.                    gettext("The Profile Repository server returned the following error:") .
  3008.                    "\n" .  $res?$res:gettext("UNKOWN ERROR") .  "\n" .
  3009.                    gettext("Please re-enter registration information or contact the administrator.");
  3010.                 UI_Important(gettext("Login Error\n") . $errmsg);
  3011.             }
  3012.         } else {
  3013.             my ($status_ok,$res) = user_login($url, $user, $pass);
  3014.             if ($status_ok) {
  3015.                 $signup_okay = 1;
  3016.             } else {
  3017.                 my $errmsg = gettext("Login failure\n Please check username and password and try again.") . "\n" . $res;
  3018.                 UI_Important($errmsg);
  3019.             }
  3020.         }
  3021.     } until $signup_okay;
  3022.  
  3023.     $repo_cfg->{repository}{user} = $user;
  3024.     $repo_cfg->{repository}{pass} = $pass;
  3025.     $repo_cfg->{repository}{email} = $email;
  3026.  
  3027.     write_config("repository.conf", $repo_cfg) if ( $save_config eq "y" );
  3028.  
  3029.     return ($user, $pass);
  3030. }
  3031.  
  3032. sub UI_ask_to_enable_repo {
  3033.  
  3034.     my $q = { };
  3035.     return if ( not defined $cfg->{repository}{url} );
  3036.     $q->{headers} = [
  3037.       "Repository", $cfg->{repository}{url},
  3038.     ];
  3039.     $q->{explanation} = gettext( "Would you like to enable access to the
  3040. profile repository?" ); $q->{functions} = [ "CMD_ENABLE_REPO",
  3041. "CMD_DISABLE_REPO", "CMD_ASK_LATER", ];
  3042.  
  3043.     my $cmd;
  3044.     do {
  3045.         $cmd = UI_PromptUser($q);
  3046.     } until $cmd =~ /^CMD_(ENABLE_REPO|DISABLE_REPO|ASK_LATER)/;
  3047.  
  3048.     if ($cmd eq "CMD_ENABLE_REPO") {
  3049.         $repo_cfg->{repository}{enabled} = "yes";
  3050.     } elsif ($cmd eq "CMD_DISABLE_REPO") {
  3051.         $repo_cfg->{repository}{enabled} = "no";
  3052.     } elsif ($cmd eq "CMD_ASK_LATER") {
  3053.         $repo_cfg->{repository}{enabled} = "later";
  3054.     }
  3055.  
  3056.     eval { write_config("repository.conf", $repo_cfg) };
  3057.     if ($@) {
  3058.         fatal_error($@);
  3059.     }
  3060. }
  3061.  
  3062.  
  3063. sub UI_ask_to_upload_profiles {
  3064.  
  3065.     my $q = { };
  3066.     $q->{headers} = [
  3067.       "Repository", $cfg->{repository}{url},
  3068.     ];
  3069.     $q->{explanation} =
  3070.       gettext( "Would you like to upload newly created and changed profiles to
  3071.       the profile repository?" );
  3072.     $q->{functions} = [
  3073.       "CMD_YES", "CMD_NO", "CMD_ASK_LATER",
  3074.     ];
  3075.  
  3076.     my $cmd;
  3077.     do {
  3078.         $cmd = UI_PromptUser($q);
  3079.     } until $cmd =~ /^CMD_(YES|NO|ASK_LATER)/;
  3080.  
  3081.     if ($cmd eq "CMD_NO") {
  3082.         $repo_cfg->{repository}{upload} = "no";
  3083.     } elsif ($cmd eq "CMD_YES") {
  3084.         $repo_cfg->{repository}{upload} = "yes";
  3085.     } elsif ($cmd eq "CMD_ASK_LATER") {
  3086.         $repo_cfg->{repository}{upload} = "later";
  3087.     }
  3088.  
  3089.     eval { write_config("repository.conf", $repo_cfg) };
  3090.     if ($@) {
  3091.         fatal_error($@);
  3092.     }
  3093. }
  3094.  
  3095.  
  3096. sub parse_repo_profile {
  3097.     my ($fqdbin, $repo_url, $profile) = @_;
  3098.  
  3099.     my $profile_data = eval {
  3100.         parse_profile_data($profile->{profile}, getprofilefilename($fqdbin), 0);
  3101.     };
  3102.     if ($@) {
  3103.         print STDERR "PARSING ERROR: $@\n";
  3104.         $profile_data = undef;
  3105.     }
  3106.  
  3107.     if ($profile_data) {
  3108.         set_repo_info($profile_data->{$fqdbin}{$fqdbin}, $repo_url,
  3109.                       $profile->{username}, $profile->{id});
  3110.     }
  3111.  
  3112.     return $profile_data;
  3113. }
  3114.  
  3115.  
  3116. sub set_repo_info {
  3117.     my ($profile_data, $repo_url, $username, $id) = @_;
  3118.  
  3119.     # save repository metadata
  3120.     $profile_data->{repo}{url}  = $repo_url;
  3121.     $profile_data->{repo}{user} = $username;
  3122.     $profile_data->{repo}{id}   = $id;
  3123. }
  3124.  
  3125.  
  3126. sub is_repo_profile {
  3127.     my $profile_data = shift;
  3128.  
  3129.     return $profile_data->{repo}{url}  &&
  3130.            $profile_data->{repo}{user} &&
  3131.            $profile_data->{repo}{id};
  3132. }
  3133.  
  3134.  
  3135. sub get_repo_user_pass {
  3136.     my ($user, $pass);
  3137.  
  3138.     if ($repo_cfg) {
  3139.         $user = $repo_cfg->{repository}{user};
  3140.         $pass = $repo_cfg->{repository}{pass};
  3141.     }
  3142.  
  3143.     unless ($user && $pass) {
  3144.         ($user, $pass) = UI_repo_signup();
  3145.     }
  3146.  
  3147.     return ($user, $pass);
  3148. }
  3149.  
  3150.  
  3151. sub get_preferred_user ($) {
  3152.     my $repo_url = shift;
  3153.     return $cfg->{repository}{preferred_user} || "NOVELL";
  3154. }
  3155.  
  3156.  
  3157. sub repo_is_enabled () {
  3158.     my $enabled;
  3159.     if ($cfg->{repository}{url} &&
  3160.         $repo_cfg &&
  3161.         $repo_cfg->{repository}{enabled} &&
  3162.         $repo_cfg->{repository}{enabled} eq "yes") {
  3163.         $enabled = 1;
  3164.     }
  3165.     return $enabled;
  3166. }
  3167.  
  3168.  
  3169. sub update_repo_profile {
  3170.     my $profile = shift;
  3171.  
  3172.     return undef if ( not is_repo_profile($profile) );
  3173.     my $distro = $cfg->{repository}{distro};
  3174.     my $url    = $profile->{repo}{url};
  3175.     my $user   = $profile->{repo}{user};
  3176.     my $id     = $profile->{repo}{id};
  3177.  
  3178.     UI_BusyStart( gettext("Connecting to repository.....") );
  3179.     my ($status_ok,$res) = fetch_newer_profile( $url,
  3180.                                                 $distro,
  3181.                                                 $user,
  3182.                                                 $id,
  3183.                                                 $profile->{name}
  3184.                                               );
  3185.     UI_BusyStop();
  3186.     if ( ! $status_ok ) {
  3187.         my $errmsg =
  3188.           sprintf(
  3189.             gettext("WARNING: Profile update check failed\nError Detail:\n%s"),
  3190.             defined $res?$res:gettext("UNKNOWN ERROR"));
  3191.         UI_Important($errmsg);
  3192.         $res = undef;
  3193.     }
  3194.     return( $res );
  3195. }
  3196.  
  3197. sub UI_ask_mode_toggles ($$$) {
  3198.     my ($audit_toggle, $owner_toggle, $oldmode) = @_;
  3199.     my $q = { };
  3200.     $q->{headers} = [ ];
  3201. #      "Repository", $cfg->{repository}{url},
  3202. #    ];
  3203.     $q->{explanation} = gettext( "Change mode modifiers");
  3204.  
  3205.     if ($audit_toggle) {
  3206.     $q->{functions} = [ "CMD_AUDIT_OFF" ];
  3207.     } else {
  3208.     $q->{functions} = [ "CMD_AUDIT_NEW" ];
  3209.     push @{$q->{functions}}, "CMD_AUDIT_FULL" if ($oldmode);
  3210.     }
  3211.  
  3212.     if ($owner_toggle) {
  3213.     push @{$q->{functions}}, "CMD_USER_OFF";
  3214.     } else {
  3215.     push @{$q->{functions}}, "CMD_USER_ON";
  3216.     }
  3217.     push @{$q->{functions}}, "CMD_CONTINUE";
  3218.  
  3219.     my $cmd;
  3220.     do {
  3221.         $cmd = UI_PromptUser($q);
  3222.     } until $cmd =~ /^CMD_(AUDIT_OFF|AUDIT_NEW|AUDIT_FULL|USER_ON|USER_OFF|CONTINUE)/;
  3223.  
  3224.     if ($cmd eq "CMD_AUDIT_OFF") {
  3225.     $audit_toggle = 0;
  3226.     } elsif ($cmd eq "CMD_AUDIT_NEW") {
  3227.     $audit_toggle = 1;
  3228.     } elsif ($cmd eq "CMD_AUDIT_FULL") {
  3229.     $audit_toggle = 2;
  3230.     } elsif ($cmd eq "CMD_USER_ON") {
  3231.     $owner_toggle = 1;
  3232.     } elsif ($cmd eq "CMD_USER_OFF") {
  3233.     $owner_toggle = 0;
  3234. #    $owner_toggle++;
  3235. #    $owner_toggle++ if (!$oldmode && $owner_toggle == 2);
  3236. #    $owner_toggle = 0 if ($owner_toggle > 3);
  3237.     }
  3238.     return ($audit_toggle, $owner_toggle);
  3239. }
  3240.  
  3241. sub ask_the_questions {
  3242.     my $found; # do the magic foo-foo
  3243.     for my $sdmode (sort keys %log) {
  3244.  
  3245.         # let them know what sort of changes we're about to list...
  3246.         if ($sdmode eq "PERMITTING") {
  3247.             UI_Info(gettext("Complain-mode changes:"));
  3248.         } elsif ($sdmode eq "REJECTING") {
  3249.             UI_Info(gettext("Enforce-mode changes:"));
  3250.         } else {
  3251.  
  3252.             # if we're not permitting and not rejecting, something's broken.
  3253.             # most likely  the code we're using to build the hash tree of log
  3254.             # entries - this should never ever happen
  3255.             fatal_error(sprintf(gettext('Invalid mode found: %s'), $sdmode));
  3256.         }
  3257.  
  3258.         for my $profile (sort keys %{ $log{$sdmode} }) {
  3259.             my $p = update_repo_profile($sd{$profile}{$profile});
  3260.             UI_SelectUpdatedRepoProfile($profile, $p) if ( $p );
  3261.  
  3262.             $found++;
  3263.  
  3264.             # this sorts the list of hats, but makes sure that the containing
  3265.             # profile shows up in the list first to keep the question order
  3266.             # rational
  3267.             my @hats =
  3268.               grep { $_ ne $profile } keys %{ $log{$sdmode}{$profile} };
  3269.             unshift @hats, $profile
  3270.               if defined $log{$sdmode}{$profile}{$profile};
  3271.  
  3272.             for my $hat (@hats) {
  3273.  
  3274.                 # step through all the capabilities first...
  3275.                 for my $capability (sort keys %{ $log{$sdmode}{$profile}{$hat}{capability} }) {
  3276.  
  3277.                     # we don't care about it if we've already added it to the
  3278.                     # profile
  3279.                     next if profile_known_capability($sd{$profile}{$hat},
  3280.                              $capability);
  3281.  
  3282.                     my $severity = $sevdb->rank(uc("cap_$capability"));
  3283.  
  3284.                     my $defaultoption = 1;
  3285.                     my @options       = ();
  3286.                     my @newincludes;
  3287.                     @newincludes = matchcapincludes($sd{$profile}{$hat},
  3288.                                                     $capability);
  3289.  
  3290.  
  3291.                     my $q = {};
  3292.  
  3293.                     if (@newincludes) {
  3294.                         push @options,
  3295.                           map { "#include <$_>" } sort(uniq(@newincludes));
  3296.                     }
  3297.  
  3298.                     if ( @options ) {
  3299.                         push @options, "capability $capability";
  3300.                         $q->{options}  = [@options];
  3301.                         $q->{selected} = $defaultoption - 1;
  3302.                     }
  3303.  
  3304.                     $q->{headers} = [];
  3305.                     push @{ $q->{headers} }, gettext("Profile"), combine_name($profile, $hat);
  3306.                     push @{ $q->{headers} }, gettext("Capability"), $capability;
  3307.                     push @{ $q->{headers} }, gettext("Severity"),   $severity;
  3308.  
  3309.             my $audit_toggle = 0;
  3310.             $q->{functions} = [
  3311.             "CMD_ALLOW", "CMD_DENY", "CMD_AUDIT_NEW", "CMD_ABORT", "CMD_FINISHED"
  3312.             ];
  3313.  
  3314.                     # complain-mode events default to allow - enforce defaults
  3315.                     # to deny
  3316.                     $q->{default} = ($sdmode eq "PERMITTING") ? "CMD_ALLOW" : "CMD_DENY";
  3317.  
  3318.                     $seenevents++;
  3319.                     my $done = 0;
  3320.                     while ( not $done ) {
  3321.                         # what did the grand exalted master tell us to do?
  3322.                         my ($ans, $selected) = UI_PromptUser($q);
  3323.  
  3324.             if ($ans =~ /^CMD_AUDIT/) {
  3325.                 $audit_toggle = !$audit_toggle;
  3326.                 my $audit = "";
  3327.                 if ($audit_toggle) {
  3328.                 $q->{functions} = [
  3329.                     "CMD_ALLOW", "CMD_DENY", "CMD_AUDIT_OFF", "CMD_ABORT", "CMD_FINISHED"
  3330.                     ];
  3331.                 $audit = "audit ";
  3332.                 } else {
  3333.                 $q->{functions} = [
  3334.                     "CMD_ALLOW", "CMD_DENY", "CMD_AUDIT_NEW", "CMD_ABORT", "CMD_FINISHED"
  3335.                     ];
  3336.                 }
  3337.                 $q->{headers} = [];
  3338.                 push @{ $q->{headers} }, gettext("Profile"), combine_name($profile, $hat);
  3339.                 push @{ $q->{headers} }, gettext("Capability"), $audit . $capability;
  3340.                 push @{ $q->{headers} }, gettext("Severity"),   $severity;
  3341.  
  3342.                         } if ($ans eq "CMD_ALLOW") {
  3343.  
  3344.                             # they picked (a)llow, so...
  3345.  
  3346.                             my $selection = $options[$selected];
  3347.                             $done = 1;
  3348.                             if ($selection &&
  3349.                                 $selection =~ m/^#include <(.+)>$/) {
  3350.                                 my $deleted = 0;
  3351.                                 my $inc = $1;
  3352.                                 $deleted = delete_duplicates($sd{$profile}{$hat},
  3353.                                                                $inc
  3354.                                                              );
  3355.                                 $sd{$profile}{$hat}{include}{$inc} = 1;
  3356.  
  3357.                                 $changed{$profile} = 1;
  3358.                                 UI_Info(sprintf(
  3359.                                   gettext('Adding #include <%s> to profile.'),
  3360.                                           $inc));
  3361.                                 UI_Info(sprintf(
  3362.                                   gettext('Deleted %s previous matching profile entries.'),
  3363.                                            $deleted)) if $deleted;
  3364.                             }
  3365.                             # stick the capability into the profile
  3366.                             $sd{$profile}{$hat}{allow}{capability}{$capability}{set} = 1;
  3367.                             $sd{$profile}{$hat}{allow}{capability}{$capability}{audit} = $audit_toggle;
  3368.  
  3369.                             # mark this profile as changed
  3370.                             $changed{$profile} = 1;
  3371.                             $done = 1;
  3372.                             # give a little feedback to the user
  3373.                             UI_Info(sprintf(gettext('Adding capability %s to profile.'), $capability));
  3374.                         } elsif ($ans eq "CMD_DENY") {
  3375.                             $sd{$profile}{$hat}{deny}{capability}{$capability}{set} = 1;
  3376.                             # mark this profile as changed
  3377.                             $changed{$profile} = 1;
  3378.                             UI_Info(sprintf(gettext('Denying capability %s to profile.'), $capability));
  3379.                             $done = 1;
  3380.                         } else {
  3381.                             redo;
  3382.                         }
  3383.                     }
  3384.                 }
  3385.  
  3386.                 # and then step through all of the path entries...
  3387.                 for my $path (sort keys %{ $log{$sdmode}{$profile}{$hat}{path} }) {
  3388.  
  3389.                     my $mode = $log{$sdmode}{$profile}{$hat}{path}{$path};
  3390.  
  3391.             # do original profile lookup once.
  3392.  
  3393.             my $allow_mode = 0;
  3394.             my $allow_audit = 0;
  3395.             my $deny_mode = 0;
  3396.             my $deny_audit = 0;
  3397.  
  3398.             my ($fmode, $famode, $imode, $iamode, @fm, @im, $cm, $am, $cam, @m);
  3399.             ($fmode, $famode, @fm) = rematchfrag($sd{$profile}{$hat}, 'allow', $path);
  3400.             $allow_mode |= $fmode if $fmode;
  3401.             $allow_audit |= $famode if $famode;
  3402.             ($imode, $iamode, @im) = match_prof_incs_to_path($sd{$profile}{$hat}, 'allow', $path);
  3403.             $allow_mode |= $imode if $imode;
  3404.             $allow_audit |= $iamode if $iamode;
  3405.  
  3406.             ($cm, $cam, @m) = rematchfrag($sd{$profile}{$hat}, 'deny', $path);
  3407.             $deny_mode |= $cm if $cm;
  3408.             $deny_audit |= $cam if $cam;
  3409.             ($cm, $cam, @m) = match_prof_incs_to_path($sd{$profile}{$hat}, 'deny', $path);
  3410.             $deny_mode |= $cm if $cm;
  3411.             $deny_audit |= $cam if $cam;
  3412.  
  3413.             if ($deny_mode & $AA_MAY_EXEC) {
  3414.             $deny_mode |= $ALL_AA_EXEC_TYPE;
  3415.             }
  3416.  
  3417.             # mask off the modes that have been denied
  3418.             $mode &= ~$deny_mode;
  3419.             $allow_mode &= ~$deny_mode;
  3420.  
  3421.                     # if we had an access(X_OK) request or some other kind of
  3422.                     # event that generates a "PERMITTING x" syslog entry,
  3423.                     # first check if it was already dealt with by a i/p/x
  3424.                     # question due to a exec().  if not, ask about adding ix
  3425.                     # permission.
  3426.                     if ($mode & $AA_MAY_EXEC) {
  3427.  
  3428.                         # get rid of the access() markers.
  3429.                         $mode &= (~$ALL_AA_EXEC_TYPE);
  3430.  
  3431.                         unless ($allow_mode & $allow_mode & $AA_MAY_EXEC) {
  3432.                             $mode |= str_to_mode("ix");
  3433.                         }
  3434.                     }
  3435.  
  3436.                     # if we had an mmap(PROT_EXEC) request, first check if we
  3437.                     # already have added an ix rule to the profile
  3438.                     if ($mode & $AA_EXEC_MMAP) {
  3439.                         # ix implies m.  don't ask if they want to add an "m"
  3440.                         # rule when we already have a matching ix rule.
  3441.                         if ($allow_mode && contains($allow_mode, "ix")) {
  3442.                             $mode &= (~$AA_EXEC_MMAP);
  3443.                         }
  3444.                     }
  3445.  
  3446.                     next unless $mode;
  3447.  
  3448.  
  3449.                     my @matches;
  3450.  
  3451.                     if ($fmode) {
  3452.                         push @matches, @fm;
  3453.                     }
  3454.                     if ($imode) {
  3455.                         push @matches, @im;
  3456.                     }
  3457.  
  3458.                     unless ($allow_mode && mode_contains($allow_mode, $mode)) {
  3459.  
  3460.                         my $defaultoption = 1;
  3461.                         my @options       = ();
  3462.  
  3463.                         # check the path against the available set of include
  3464.                         # files
  3465.                         my @newincludes;
  3466.                         my $includevalid;
  3467.                         for my $incname (keys %include) {
  3468.                             $includevalid = 0;
  3469.  
  3470.                             # don't suggest it if we're already including it,
  3471.                             # that's dumb
  3472.                             next if $sd{$profile}{$hat}{$incname};
  3473.  
  3474.                             # only match includes that can be suggested to
  3475.                             # the user
  3476.                 if ($cfg->{settings}{custom_includes}) {
  3477.                             for my $incm (split(/\s+/,
  3478.                                                 $cfg->{settings}{custom_includes})
  3479.                                          ) {
  3480.                                 $includevalid = 1 if $incname =~ /$incm/;
  3481.                             }
  3482.                 }
  3483.                             $includevalid = 1 if $incname =~ /abstractions/;
  3484.                             next if ($includevalid == 0);
  3485.  
  3486.                             ($cm, $am, @m) = match_include_to_path($incname, 'allow', $path);
  3487.                             if ($cm && mode_contains($cm, $mode)) {
  3488.                 #make sure it doesn't deny $mode
  3489.                 my $dm = match_include_to_path($incname, 'deny', $path);
  3490.                 unless (($mode & $dm) || (grep { $_ eq "/**" } @m)) {
  3491.                                     push @newincludes, $incname;
  3492.                                 }
  3493.                             }
  3494.                         }
  3495.  
  3496.  
  3497.                         # did any match?  add them to the option list...
  3498.                         if (@newincludes) {
  3499.                             push @options,
  3500.                               map { "#include <$_>" }
  3501.                               sort(uniq(@newincludes));
  3502.                         }
  3503.  
  3504.                         # include the literal path in the option list...
  3505.                         push @options, $path;
  3506.  
  3507.                         # match the current path against the globbing list in
  3508.                         # logprof.conf
  3509.                         my @globs = globcommon($path);
  3510.                         if (@globs) {
  3511.                             push @matches, @globs;
  3512.                         }
  3513.  
  3514.                         # suggest any matching globs the user manually entered
  3515.                         for my $userglob (@userglobs) {
  3516.                             push @matches, $userglob
  3517.                               if matchliteral($userglob, $path);
  3518.                         }
  3519.  
  3520.                         # we'll take the cheesy way and order the suggested
  3521.                         # globbing list by length, which is usually right,
  3522.                         # but not always always
  3523.                         push @options,
  3524.                           sort { length($b) <=> length($a) }
  3525.                           grep { $_ ne $path }
  3526.                           uniq(@matches);
  3527.                         $defaultoption = $#options + 1;
  3528.  
  3529.                         my $severity = $sevdb->rank($path, mode_to_str($mode));
  3530.  
  3531.             my $audit_toggle = 0;
  3532.             my $owner_toggle = $cfg->{settings}{default_owner_prompt};
  3533.                         my $done = 0;
  3534.                         while (not $done) {
  3535.  
  3536.                             my $q = {};
  3537.                             $q->{headers} = [];
  3538.                             push @{ $q->{headers} }, gettext("Profile"), combine_name($profile, $hat);
  3539.                             push @{ $q->{headers} }, gettext("Path"), $path;
  3540.  
  3541.                             # merge in any previous modes from this run
  3542.                             if ($allow_mode) {
  3543.                 my $str;
  3544. #print "mode: " . print_mode($mode) . " allow: " . print_mode($allow_mode) . "\n";
  3545.                                 $mode |= $allow_mode;
  3546.                 my $tail;
  3547.                 my $prompt_mode;
  3548.                 if ($owner_toggle == 0) {
  3549.                     $prompt_mode = flatten_mode($mode);
  3550.                     $tail = "     " . gettext("(owner permissions off)");
  3551.                 } elsif ($owner_toggle == 1) {
  3552.                     $prompt_mode = $mode;
  3553.                     $tail = "";
  3554.                 } elsif ($owner_toggle == 2) {
  3555.                     $prompt_mode = $allow_mode | owner_flatten_mode($mode & ~$allow_mode);
  3556.                     $tail = "     " . gettext("(force new perms to owner)");
  3557.                 } else {
  3558.                     $prompt_mode = owner_flatten_mode($mode);
  3559.                     $tail = "     " . gettext("(force all rule perms to owner)");
  3560.                 }
  3561.  
  3562.                 if ($audit_toggle == 1) {
  3563.                     $str = mode_to_str_user($allow_mode);
  3564.                     $str .= ", " if ($allow_mode);
  3565.                     $str .= "audit " . mode_to_str_user($prompt_mode & ~$allow_mode) . $tail;
  3566.                 } elsif ($audit_toggle == 2) {
  3567.                     $str = "audit " . mode_to_str_user($prompt_mode) . $tail;
  3568.                 } else {
  3569.                     $str = mode_to_str_user($prompt_mode) . $tail;
  3570.                 }
  3571.                                 push @{ $q->{headers} }, gettext("Old Mode"), mode_to_str_user($allow_mode);
  3572.                                 push @{ $q->{headers} }, gettext("New Mode"), $str;
  3573.                             } else {
  3574.                 my $str = "";
  3575.                 if ($audit_toggle) {
  3576.                     $str = "audit ";
  3577.                 }
  3578.                 my $tail;
  3579.                 my $prompt_mode;
  3580.                 if ($owner_toggle == 0) {
  3581.                     $prompt_mode = flatten_mode($mode);
  3582.                     $tail = "     " . gettext("(owner permissions off)");
  3583.                 } elsif ($owner_toggle == 1) {
  3584.                     $prompt_mode = $mode;
  3585.                     $tail = "";
  3586.                 } else {
  3587.                     $prompt_mode = owner_flatten_mode($mode);
  3588.                     $tail = "     " . gettext("(force perms to owner)");
  3589.                 }
  3590.                 $str .= mode_to_str_user($prompt_mode) . $tail;
  3591.                                 push @{ $q->{headers} }, gettext("Mode"), $str; 
  3592.                             }
  3593.                             push @{ $q->{headers} }, gettext("Severity"), $severity;
  3594.  
  3595.                             $q->{options}  = [@options];
  3596.                             $q->{selected} = $defaultoption - 1;
  3597.  
  3598.                             $q->{functions} = [
  3599.                               "CMD_ALLOW", "CMD_DENY", "CMD_GLOB", "CMD_GLOBEXT", "CMD_NEW",
  3600.                               "CMD_ABORT", "CMD_FINISHED", "CMD_OTHER"
  3601.                             ];
  3602.  
  3603.                             $q->{default} =
  3604.                               ($sdmode eq "PERMITTING")
  3605.                               ? "CMD_ALLOW"
  3606.                               : "CMD_DENY";
  3607.  
  3608.                             $seenevents++;
  3609.                             # if they just hit return, use the default answer
  3610.                             my ($ans, $selected) = UI_PromptUser($q);
  3611.  
  3612.                 if ($ans eq "CMD_OTHER") {
  3613.  
  3614.                 ($audit_toggle, $owner_toggle) = UI_ask_mode_toggles($audit_toggle, $owner_toggle, $allow_mode);
  3615.                 } elsif ($ans eq "CMD_USER_TOGGLE") {
  3616.                 $owner_toggle++;
  3617.                 $owner_toggle++ if (!$allow_mode && $owner_toggle == 2);
  3618.                 $owner_toggle = 0 if ($owner_toggle > 3);
  3619.                 } elsif ($ans eq "CMD_ALLOW") {
  3620.                                 $path = $options[$selected];
  3621.                                 $done = 1;
  3622.                                 if ($path =~ m/^#include <(.+)>$/) {
  3623.                                     my $inc = $1;
  3624.                                     my $deleted = 0;
  3625.  
  3626.                                     $deleted = delete_duplicates($sd{$profile}{$hat},
  3627.                                                                   $inc );
  3628.  
  3629.                                     # record the new entry
  3630.                                     $sd{$profile}{$hat}{include}{$inc} = 1;
  3631.  
  3632.                                     $changed{$profile} = 1;
  3633.                                     UI_Info(sprintf(gettext('Adding #include <%s> to profile.'), $inc));
  3634.                                     UI_Info(sprintf(gettext('Deleted %s previous matching profile entries.'), $deleted)) if $deleted;
  3635.                                 } else {
  3636.                                     if ($sd{$profile}{$hat}{allow}{path}{$path}{mode}) {
  3637.                                         $mode = $mode | $sd{$profile}{$hat}{allow}{path}{$path}{mode};
  3638.                                     }
  3639.  
  3640.                                     my $deleted = 0;
  3641.                                     for my $entry (keys %{ $sd{$profile}{$hat}{allow}{path} }) {
  3642.  
  3643.                                         next if $path eq $entry;
  3644.  
  3645.                                         if (matchregexp($path, $entry)) {
  3646.  
  3647.                                             # regexp matches, add it's mode to
  3648.                                             # the list to check against
  3649.                                             if (mode_contains($mode,
  3650.                                                 $sd{$profile}{$hat}{allow}{path}{$entry}{mode})) {
  3651.                                                 delete $sd{$profile}{$hat}{allow}{path}{$entry};
  3652.                                                 $deleted++;
  3653.                                             }
  3654.                                         }
  3655.                                     }
  3656.  
  3657.                                     # record the new entry
  3658.                     if ($owner_toggle == 0) {
  3659.                     $mode = flatten_mode($mode);
  3660.                     } elsif ($owner_toggle == 1) {
  3661.                     $mode = $mode;
  3662.                     } elsif ($owner_toggle == 2) {
  3663.                     $mode = $allow_mode | owner_flatten_mode($mode & ~$allow_mode);
  3664.                     } elsif  ($owner_toggle == 3) {
  3665.                     $mode = owner_flatten_mode($mode);
  3666.                     }
  3667.                                     $sd{$profile}{$hat}{allow}{path}{$path}{mode} = $mode;
  3668.                     my $tmpmode = ($audit_toggle == 1) ? $mode & ~$allow_mode : 0;
  3669.                     $tmpmode = ($audit_toggle == 2) ? $mode : $tmpmode;
  3670.                                     $sd{$profile}{$hat}{allow}{path}{$path}{audit} |= $tmpmode;
  3671.  
  3672.                                     $changed{$profile} = 1;
  3673.                                     UI_Info(sprintf(gettext('Adding %s %s to profile.'), $path, mode_to_str_user($mode)));
  3674.                                     UI_Info(sprintf(gettext('Deleted %s previous matching profile entries.'), $deleted)) if $deleted;
  3675.                                 }
  3676.                             } elsif ($ans eq "CMD_DENY") {
  3677.                 # record the new entry
  3678.                 $sd{$profile}{$hat}{deny}{path}{$path}{mode} |= $mode & ~$allow_mode;
  3679.                 $sd{$profile}{$hat}{deny}{path}{$path}{audit} |= 0;
  3680.  
  3681.                 $changed{$profile} = 1;
  3682.  
  3683.                                 # go on to the next entry without saving this
  3684.                                 # one
  3685.                                 $done = 1;
  3686.                             } elsif ($ans eq "CMD_NEW") {
  3687.                                 my $arg = $options[$selected];
  3688.                                 if ($arg !~ /^#include/) {
  3689.                                     $ans = UI_GetString(gettext("Enter new path: "), $arg);
  3690.                                     if ($ans) {
  3691.                                         unless (matchliteral($ans, $path)) {
  3692.                                             my $ynprompt = gettext("The specified path does not match this log entry:") . "\n\n";
  3693.                                             $ynprompt .= "  " . gettext("Log Entry") . ":    $path\n";
  3694.                                             $ynprompt .= "  " . gettext("Entered Path") . ": $ans\n\n";
  3695.                                             $ynprompt .= gettext("Do you really want to use this path?") . "\n";
  3696.  
  3697.                                             # we default to no if they just hit return...
  3698.                                             my $key = UI_YesNo($ynprompt, "n");
  3699.  
  3700.                                             next if $key eq "n";
  3701.                                         }
  3702.  
  3703.                                         # save this one for later
  3704.                                         push @userglobs, $ans;
  3705.  
  3706.                                         push @options, $ans;
  3707.                                         $defaultoption = $#options + 1;
  3708.                                     }
  3709.                                 }
  3710.                             } elsif ($ans eq "CMD_GLOB") {
  3711.  
  3712.                                 # do globbing if they don't have an include
  3713.                                 # selected
  3714.                                 my $newpath = $options[$selected];
  3715.                                 chomp $newpath ;
  3716.                                 unless ($newpath =~ /^#include/) {
  3717.                                     # is this entry directory specific
  3718.                                     if ( $newpath =~ m/\/$/ ) {
  3719.                                         # do we collapse to /* or /**?
  3720.                                         if ($newpath =~ m/\/\*{1,2}\/$/) {
  3721.                                             $newpath =~
  3722.                                             s/\/[^\/]+\/\*{1,2}\/$/\/\*\*\//;
  3723.                                         } else {
  3724.                                             $newpath =~ s/\/[^\/]+\/$/\/\*\//;
  3725.                                         }
  3726.                                     } else {
  3727.                                         # do we collapse to /* or /**?
  3728.                                         if ($newpath =~ m/\/\*{1,2}$/) {
  3729.                                             $newpath =~ s/\/[^\/]+\/\*{1,2}$/\/\*\*/;
  3730.                                         } else {
  3731.                                             $newpath =~ s/\/[^\/]+$/\/\*/;
  3732.                                         }
  3733.                                     }
  3734.                                     if ($newpath ne $selected) {
  3735.                                         push @options, $newpath;
  3736.                                         $defaultoption = $#options + 1;
  3737.                                     }
  3738.                                 }
  3739.                             } elsif ($ans eq "CMD_GLOBEXT") {
  3740.  
  3741.                                 # do globbing if they don't have an include
  3742.                                 # selected
  3743.                                 my $newpath = $options[$selected];
  3744.                                 unless ($newpath =~ /^#include/) {
  3745.                                     # do we collapse to /*.ext or /**.ext?
  3746.                                     if ($newpath =~ m/\/\*{1,2}\.[^\/]+$/) {
  3747.                                         $newpath =~ s/\/[^\/]+\/\*{1,2}(\.[^\/]+)$/\/\*\*$1/;
  3748.                                     } else {
  3749.                                         $newpath =~ s/\/[^\/]+(\.[^\/]+)$/\/\*$1/;
  3750.                                     }
  3751.                                     if ($newpath ne $selected) {
  3752.                                         push @options, $newpath;
  3753.                                         $defaultoption = $#options + 1;
  3754.                                     }
  3755.                                 }
  3756.                             } elsif ($ans =~ /\d/) {
  3757.                                 $defaultoption = $ans;
  3758.                             }
  3759.                         }
  3760.                     }
  3761.                 }
  3762.  
  3763.                 # and then step through all of the netdomain entries...
  3764.                 for my $family (sort keys %{$log{$sdmode}
  3765.                                                 {$profile}
  3766.                                                 {$hat}
  3767.                                                 {netdomain}}) {
  3768.  
  3769.                     # TODO - severity handling for net toggles
  3770.                     #my $severity = $sevdb->rank();
  3771.                     for my $sock_type (sort keys %{$log{$sdmode}
  3772.                                                        {$profile}
  3773.                                                        {$hat}
  3774.                                                        {netdomain}
  3775.                                                        {$family}}) {
  3776.  
  3777.                         # we don't care about it if we've already added it to the
  3778.                         # profile
  3779.                         next if ( profile_known_network($sd{$profile}{$hat},
  3780.                             $family,
  3781.                             $sock_type));
  3782.                         my $defaultoption = 1;
  3783.                         my @options       = ();
  3784.                         my @newincludes;
  3785.                         @newincludes = matchnetincludes($sd{$profile}{$hat},
  3786.                                                         $family,
  3787.                                                         $sock_type);
  3788.  
  3789.                         my $q = {};
  3790.  
  3791.                         if (@newincludes) {
  3792.                             push @options,
  3793.                               map { "#include <$_>" } sort(uniq(@newincludes));
  3794.                         }
  3795.  
  3796.                         if ( @options ) {
  3797.                             push @options, "network $family $sock_type";
  3798.                             $q->{options}  = [@options];
  3799.                             $q->{selected} = $defaultoption - 1;
  3800.                         }
  3801.  
  3802.                         $q->{headers} = [];
  3803.                         push @{ $q->{headers} },
  3804.                              gettext("Profile"),
  3805.                              combine_name($profile, $hat);
  3806.                         push @{ $q->{headers} },
  3807.                              gettext("Network Family"),
  3808.                              $family;
  3809.                         push @{ $q->{headers} },
  3810.                              gettext("Socket Type"),
  3811.                              $sock_type;
  3812.  
  3813.             my $audit_toggle = 0;
  3814.  
  3815.                         $q->{functions} = [
  3816.                                             "CMD_ALLOW",
  3817.                                             "CMD_DENY",
  3818.                         "CMD_AUDIT_NEW",
  3819.                                             "CMD_ABORT",
  3820.                                             "CMD_FINISHED"
  3821.                                           ];
  3822.  
  3823.                         # complain-mode events default to allow - enforce defaults
  3824.                         # to deny
  3825.                         $q->{default} = ($sdmode eq "PERMITTING") ? "CMD_ALLOW" :
  3826.                                                                     "CMD_DENY";
  3827.  
  3828.                         $seenevents++;
  3829.  
  3830.                         # what did the grand exalted master tell us to do?
  3831.                         my $done = 0;
  3832.                         while ( not $done ) {
  3833.                             my ($ans, $selected) = UI_PromptUser($q);
  3834.                 if ($ans =~ /^CMD_AUDIT/) {
  3835.                 $audit_toggle = !$audit_toggle;
  3836.                 my $audit = $audit_toggle ? "audit " : "";
  3837.                 if ($audit_toggle) {
  3838.                     $q->{functions} = [
  3839.                     "CMD_ALLOW",
  3840.                     "CMD_DENY",
  3841.                     "CMD_AUDIT_OFF",
  3842.                     "CMD_ABORT",
  3843.                     "CMD_FINISHED"
  3844.                     ];
  3845.                 } else {
  3846.                     $q->{functions} = [
  3847.                     "CMD_ALLOW",
  3848.                     "CMD_DENY",
  3849.                     "CMD_AUDIT_NEW",
  3850.                     "CMD_ABORT",
  3851.                     "CMD_FINISHED"
  3852.                     ];
  3853.                 }
  3854.                 $q->{headers} = [];
  3855.                 push @{ $q->{headers} },
  3856.                 gettext("Profile"),
  3857.                 combine_name($profile, $hat);
  3858.                 push @{ $q->{headers} },
  3859.                 gettext("Network Family"),
  3860.                 $audit . $family;
  3861.                 push @{ $q->{headers} },
  3862.                 gettext("Socket Type"),
  3863.                 $sock_type;
  3864.                             } elsif ($ans eq "CMD_ALLOW") {
  3865.                                 my $selection = $options[$selected];
  3866.                                 $done = 1;
  3867.                                 if ($selection &&
  3868.                                     $selection =~ m/^#include <(.+)>$/) {
  3869.                                     my $inc = $1;
  3870.                                     my $deleted = 0;
  3871.                                     $deleted = delete_duplicates($sd{$profile}{$hat},
  3872.                                                                    $inc
  3873.                                                                  );
  3874.                                     # record the new entry
  3875.                                     $sd{$profile}{$hat}{include}{$inc} = 1;
  3876.  
  3877.                                     $changed{$profile} = 1;
  3878.                                     UI_Info(
  3879.                                       sprintf(
  3880.                                         gettext('Adding #include <%s> to profile.'),
  3881.                                                 $inc));
  3882.                                     UI_Info(
  3883.                                       sprintf(
  3884.                                         gettext('Deleted %s previous matching profile entries.'),
  3885.                                                  $deleted)) if $deleted;
  3886.                                 } else {
  3887.  
  3888.                                     # stick the whole rule into the profile
  3889.                                     $sd{$profile}
  3890.                                        {$hat}
  3891.                        {allow}
  3892.                                        {netdomain}
  3893.                        {audit}
  3894.                                        {$family}
  3895.                                        {$sock_type} = $audit_toggle;
  3896.  
  3897.                                     $sd{$profile}
  3898.                                        {$hat}
  3899.                        {allow}
  3900.                                        {netdomain}
  3901.                        {rule}
  3902.                                        {$family}
  3903.                                        {$sock_type} = 1;
  3904.  
  3905.                                     # mark this profile as changed
  3906.                                     $changed{$profile} = 1;
  3907.  
  3908.                                     # give a little feedback to the user
  3909.                                     UI_Info(sprintf(
  3910.                                            gettext('Adding network access %s %s to profile.'),
  3911.                                                     $family,
  3912.                                                     $sock_type
  3913.                                                    )
  3914.                                            );
  3915.                                 }
  3916.                             } elsif ($ans eq "CMD_DENY") {
  3917.                                 $done = 1;
  3918.                 # record the new entry
  3919.                                     $sd{$profile}
  3920.                                        {$hat}
  3921.                        {deny}
  3922.                                        {netdomain}
  3923.                        {rule}
  3924.                                        {$family}
  3925.                                        {$sock_type} = 1;
  3926.  
  3927.                 $changed{$profile} = 1;
  3928.                                 UI_Info(sprintf(
  3929.                                         gettext('Denying network access %s %s to profile.'),
  3930.                                                 $family,
  3931.                                                 $sock_type
  3932.                                                )
  3933.                                        );
  3934.                             } else {
  3935.                                 redo;
  3936.                             }
  3937.                         }
  3938.                     }
  3939.                 }
  3940.             }
  3941.         }
  3942.     }
  3943. }
  3944.  
  3945. sub delete_net_duplicates {
  3946.     my ($netrules, $incnetrules) = @_;
  3947.     my $deleted = 0;
  3948.     if ( $incnetrules && $netrules ) {
  3949.         my $incnetglob = defined $incnetrules->{all};
  3950.  
  3951.         # See which if any profile rules are matched by the include and can be
  3952.         # deleted
  3953.         for my $fam ( keys %$netrules ) {
  3954.             if ( $incnetglob || (ref($incnetrules->{rule}{$fam}) ne "HASH" &&
  3955.                                  $incnetrules->{rule}{$fam} == 1)) { # include allows
  3956.                                                                # all net or
  3957.                                                                # all fam
  3958.                 if ( ref($netrules->{rule}{$fam}) eq "HASH" ) {
  3959.                     $deleted += ( keys %{$netrules->{rule}{$fam}} );
  3960.                 } else {
  3961.                     $deleted++;
  3962.                 }
  3963.                 delete $netrules->{rule}{$fam};
  3964.             } elsif ( ref($netrules->{rule}{$fam}) ne "HASH" &&
  3965.                       $netrules->{rule}{$fam} == 1 ){
  3966.                 next; # profile has all family
  3967.             } else {
  3968.                 for my $socket_type ( keys %{$netrules->{rule}{$fam}} )  {
  3969.                     if ( defined $incnetrules->{$fam}{$socket_type} ) {
  3970.                         delete $netrules->{$fam}{$socket_type};
  3971.                         $deleted++;
  3972.                     }
  3973.                 }
  3974.             }
  3975.         }
  3976.     }
  3977.     return $deleted;
  3978. }
  3979.  
  3980. sub delete_cap_duplicates ($$) {
  3981.     my ($profilecaps, $inccaps) = @_;
  3982.     my $deleted = 0;
  3983.     if ( $profilecaps && $inccaps ) {
  3984.         for my $capname ( keys %$profilecaps ) {
  3985.             if ( defined $inccaps->{$capname}{set} && $inccaps->{$capname}{set} == 1 ) {
  3986.                delete $profilecaps->{$capname};
  3987.                $deleted++;
  3988.             }
  3989.         }
  3990.     }
  3991.     return $deleted;
  3992. }
  3993.  
  3994. sub delete_path_duplicates ($$$) {
  3995.     my ($profile, $incname, $allow) = @_;
  3996.     my $deleted = 0;
  3997.  
  3998.     for my $entry (keys %{ $profile->{$allow}{path} }) {
  3999.         next if $entry eq "#include <$incname>";
  4000.     my ($cm, $am, @m) = match_include_to_path($incname, $allow, $entry);
  4001.         if ($cm
  4002.             && mode_contains($cm, $profile->{$allow}{path}{$entry}{mode})
  4003.         && mode_contains($am, $profile->{$allow}{path}{$entry}{audit}))
  4004.         {
  4005.             delete $profile->{$allow}{path}{$entry};
  4006.             $deleted++;
  4007.         }
  4008.     }
  4009.     return $deleted;
  4010. }
  4011.  
  4012. sub delete_duplicates (\%$) {
  4013.     my ( $profile, $incname ) = @_;
  4014.     my $deleted = 0;
  4015.  
  4016.     # don't cross delete allow rules covered by denied rules as the coverage
  4017.     # may not be complete.  ie. want to deny a subset of allow, allow a subset
  4018.     # of deny with different perms.
  4019.  
  4020.     ## network rules
  4021.     $deleted += delete_net_duplicates($profile->{allow}{netdomain}, $include{$incname}{$incname}{allow}{netdomain});
  4022.     $deleted += delete_net_duplicates($profile->{deny}{netdomain}, $include{$incname}{$incname}{deny}{netdomain});
  4023.  
  4024.     ## capabilities
  4025.     $deleted += delete_cap_duplicates($profile->{allow}{capability},
  4026.                      $include{$incname}{$incname}{allow}{capability});
  4027.     $deleted += delete_cap_duplicates($profile->{deny}{capability},
  4028.                      $include{$incname}{$incname}{deny}{capability});
  4029.  
  4030.     ## paths
  4031.     $deleted += delete_path_duplicates($profile, $incname, 'allow');
  4032.     $deleted += delete_path_duplicates($profile, $incname, 'deny');
  4033.  
  4034.     return $deleted;
  4035. }
  4036.  
  4037. sub matchnetinclude ($$$) {
  4038.     my ($incname, $family, $type) = @_;
  4039.  
  4040.     my @matches;
  4041.  
  4042.     # scan the include fragments for this profile looking for matches
  4043.     my @includelist = ($incname);
  4044.     my @checked;
  4045.     while (my $name = shift @includelist) {
  4046.         push @checked, $name;
  4047.         return 1
  4048.           if netrules_access_check($include{$name}{$name}{allow}{netdomain}, $family, $type);
  4049.         # if this fragment includes others, check them too
  4050.         if (keys %{ $include{$name}{$name}{include} } &&
  4051.             (grep($name, @checked) == 0) ) {
  4052.             push @includelist, keys %{ $include{$name}{$name}{include} };
  4053.         }
  4054.     }
  4055.     return 0;
  4056. }
  4057.  
  4058. sub matchcapincludes (\%$) {
  4059.     my ($profile, $cap) = @_;
  4060.  
  4061.     # check the path against the available set of include
  4062.     # files
  4063.     my @newincludes;
  4064.     my $includevalid;
  4065.     for my $incname (keys %include) {
  4066.     $includevalid = 0;
  4067.  
  4068.     # don't suggest it if we're already including it,
  4069.     # that's dumb
  4070.     next if $profile->{include}{$incname};
  4071.  
  4072.     # only match includes that can be suggested to
  4073.     # the user
  4074.     if ($cfg->{settings}{custom_includes}) {
  4075.         for my $incm (split(/\s+/,
  4076.                 $cfg->{settings}{custom_includes})) {
  4077.         $includevalid = 1 if $incname =~ /$incm/;
  4078.         }
  4079.     }
  4080.     $includevalid = 1 if $incname =~ /abstractions/;
  4081.     next if ($includevalid == 0);
  4082.  
  4083.     push @newincludes, $incname
  4084.         if ( defined $include{$incname}{$incname}{allow}{capability}{$cap}{set} &&
  4085.          $include{$incname}{$incname}{allow}{capability}{$cap}{set} == 1 );
  4086.     }
  4087.     return @newincludes;
  4088. }
  4089.  
  4090. sub matchnetincludes (\%$$) {
  4091.     my ($profile, $family, $type) = @_;
  4092.  
  4093.     # check the path against the available set of include
  4094.     # files
  4095.     my @newincludes;
  4096.     my $includevalid;
  4097.     for my $incname (keys %include) {
  4098.     $includevalid = 0;
  4099.  
  4100.     # don't suggest it if we're already including it,
  4101.     # that's dumb
  4102.     next if $profile->{include}{$incname};
  4103.  
  4104.     # only match includes that can be suggested to
  4105.     # the user
  4106.     if ($cfg->{settings}{custom_includes}) {
  4107.         for my $incm (split(/\s+/, $cfg->{settings}{custom_includes})) {
  4108.         $includevalid = 1 if $incname =~ /$incm/;
  4109.         }
  4110.     }
  4111.     $includevalid = 1 if $incname =~ /abstractions/;
  4112.     next if ($includevalid == 0);
  4113.  
  4114.     push @newincludes, $incname
  4115.         if matchnetinclude($incname, $family, $type);
  4116.     }
  4117.     return @newincludes;
  4118. }
  4119.  
  4120.  
  4121. sub do_logprof_pass {
  4122.     my $logmark = shift || "";
  4123.  
  4124.     # zero out the state variables for this pass...
  4125.     %t              = ( );
  4126.     %transitions    = ( );
  4127.     %seen           = ( );
  4128.     %sd             = ( );
  4129.     %profilechanges = ( );
  4130.     %prelog         = ( );
  4131.     @log            = ( );
  4132.     %log            = ( );
  4133.     %changed        = ( );
  4134.     %skip           = ( );
  4135.     %filelist       = ( );
  4136.  
  4137.     UI_Info(sprintf(gettext('Reading log entries from %s.'), $filename));
  4138.     UI_Info(sprintf(gettext('Updating AppArmor profiles in %s.'), $profiledir));
  4139.  
  4140.     readprofiles();
  4141.     unless ($sevdb) {
  4142.         $sevdb = new Immunix::Severity("$confdir/severity.db", gettext("unknown
  4143. "));
  4144.     }
  4145.  
  4146.     # we need to be able to break all the way out of deep into subroutine calls
  4147.     # if they select "Finish" so we can take them back out to the genprof prompt
  4148.     eval {
  4149.         unless ($repo_cfg || not defined $cfg->{repository}{url}) {
  4150.             $repo_cfg = read_config("repository.conf");
  4151.             unless ($repo_cfg->{repository}{enabled} eq "yes" ||
  4152.                     $repo_cfg->{repository}{enabled} eq "no") {
  4153.                 UI_ask_to_enable_repo();
  4154.             }
  4155.         }
  4156.  
  4157.         read_log($logmark);
  4158.  
  4159.         for my $root (@log) {
  4160.             handlechildren(undef, undef, $root);
  4161.         }
  4162.  
  4163.         for my $pid (sort { $a <=> $b } keys %profilechanges) {
  4164.             setprocess($pid, $profilechanges{$pid});
  4165.         }
  4166.  
  4167.         collapselog();
  4168.  
  4169.         ask_the_questions();
  4170.  
  4171.         if ($UI_Mode eq "yast") {
  4172.             if (not $running_under_genprof) {
  4173.                 if ($seenevents) {
  4174.                     my $w = { type => "wizard" };
  4175.                     $w->{explanation} = gettext("The profile analyzer has completed processing the log files.\n\nAll updated profiles will be reloaded");
  4176.                     $w->{functions} = [ "CMD_ABORT", "CMD_FINISHED" ];
  4177.                     SendDataToYast($w);
  4178.                     my $foo = GetDataFromYast();
  4179.                 } else {
  4180.                     my $w = { type => "wizard" };
  4181.                     $w->{explanation} = gettext("No unhandled AppArmor events were found in the system log.");
  4182.                     $w->{functions} = [ "CMD_ABORT", "CMD_FINISHED" ];
  4183.                     SendDataToYast($w);
  4184.                     my $foo = GetDataFromYast();
  4185.                 }
  4186.             }
  4187.         }
  4188.     };
  4189.  
  4190.     my $finishing = 0;
  4191.     if ($@) {
  4192.         if ($@ =~ /FINISHING/) {
  4193.             $finishing = 1;
  4194.         } else {
  4195.             die $@;
  4196.         }
  4197.     }
  4198.  
  4199.     save_profiles();
  4200.  
  4201.     if (repo_is_enabled()) {
  4202.         if ( (not defined $repo_cfg->{repository}{upload}) ||
  4203.              ($repo_cfg->{repository}{upload} eq "later") ) {
  4204.         UI_ask_to_upload_profiles();
  4205.         }
  4206.         if ($repo_cfg->{repository}{upload} eq "yes") {
  4207.             sync_profiles();
  4208.         }
  4209.         @created = ();
  4210.     }
  4211.  
  4212.     # if they hit "Finish" we need to tell the caller that so we can exit
  4213.     # all the way instead of just going back to the genprof prompt
  4214.     return $finishing ? "FINISHED" : "NORMAL";
  4215. }
  4216.  
  4217. sub save_profiles {
  4218.     # make sure the profile changes we've made are saved to disk...
  4219.     my @changed = sort keys %changed;
  4220.     #
  4221.     # first make sure that profiles in %changed are active (or actual profiles
  4222.     # in %sd) - this is to handle the sloppiness of setting profiles as changed
  4223.     # when they are parsed in the case of legacy hat code that we want to write
  4224.     # out in an updated format
  4225.     foreach  my $profile_name ( keys %changed ) {
  4226.         if ( ! is_active_profile( $profile_name ) ) {
  4227.             delete $changed{ $profile_name };
  4228.         }
  4229.     }
  4230.     @changed = sort keys %changed;
  4231.  
  4232.     if (@changed) {
  4233.         if ($UI_Mode eq "yast") {
  4234.             my (@selected_profiles, $title, $explanation, %profile_changes);
  4235.             foreach my $prof (@changed) {
  4236.                 my $oldprofile = serialize_profile($original_sd{$prof}, $prof);
  4237.                 my $newprofile = serialize_profile($sd{$prof}, $prof);
  4238.  
  4239.                 $profile_changes{$prof} = get_profile_diff($oldprofile,
  4240.                                                            $newprofile);
  4241.             }
  4242.             $explanation = gettext("Select which profile changes you would like to save to the\nlocal profile set");
  4243.             $title       = gettext("Local profile changes");
  4244.             SendDataToYast(
  4245.                 {
  4246.                     type           => "dialog-select-profiles",
  4247.                     title          => $title,
  4248.                     explanation    => $explanation,
  4249.                     default_select => "true",
  4250.                     get_changelog  => "false",
  4251.                     profiles       => \%profile_changes
  4252.                 }
  4253.             );
  4254.             my ($ypath, $yarg) = GetDataFromYast();
  4255.             if ($yarg->{STATUS} eq "cancel") {
  4256.                 return;
  4257.             } else {
  4258.                 my $selected_profiles_ref = $yarg->{PROFILES};
  4259.                 for my $profile (@$selected_profiles_ref) {
  4260.                     writeprofile_ui_feedback($profile);
  4261.                     reload_base($profile);
  4262.                 }
  4263.             }
  4264.         } else {
  4265.             my $q = {};
  4266.             $q->{title}   = "Changed Local Profiles";
  4267.             $q->{headers} = [];
  4268.  
  4269.             $q->{explanation} =
  4270.               gettext( "The following local profiles were changed.  Would you like to save them?");
  4271.  
  4272.             $q->{functions} = [ "CMD_SAVE_CHANGES",
  4273.                                 "CMD_VIEW_CHANGES",
  4274.                                 "CMD_ABORT", ];
  4275.  
  4276.             $q->{default} = "CMD_VIEW_CHANGES";
  4277.  
  4278.             $q->{options}  = [@changed];
  4279.             $q->{selected} = 0;
  4280.  
  4281.             my ($p, $ans, $arg);
  4282.             do {
  4283.                 ($ans, $arg) = UI_PromptUser($q);
  4284.  
  4285.                 if ($ans eq "CMD_VIEW_CHANGES") {
  4286.                     my $which      = $changed[$arg];
  4287.                     my $oldprofile =
  4288.                       serialize_profile($original_sd{$which}, $which);
  4289.                     my $newprofile = serialize_profile($sd{$which}, $which);
  4290.                     display_changes($oldprofile, $newprofile);
  4291.                 }
  4292.  
  4293.             } until $ans =~ /^CMD_SAVE_CHANGES/;
  4294.  
  4295.             for my $profile (sort keys %changed) {
  4296.                 writeprofile_ui_feedback($profile);
  4297.                 reload_base($profile);
  4298.             }
  4299.         }
  4300.     }
  4301. }
  4302.  
  4303.  
  4304. sub get_pager {
  4305.  
  4306.     if ( $ENV{PAGER} and (-x "/usr/bin/$ENV{PAGER}" ||
  4307.                           -x "/usr/sbin/$ENV{PAGER}" )
  4308.        ) {
  4309.         return $ENV{PAGER};
  4310.     } else {
  4311.         return "less"
  4312.     }
  4313. }
  4314.  
  4315.  
  4316. sub display_text {
  4317.     my ($header, $body) = @_;
  4318.     my $pager = get_pager();
  4319.     if (open(PAGER, "| $pager")) {
  4320.         print PAGER "$header\n\n$body";
  4321.         close(PAGER);
  4322.     }
  4323. }
  4324.  
  4325. sub get_profile_diff {
  4326.     my ($oldprofile, $newprofile) = @_;
  4327.     my $oldtmp = new File::Temp(UNLINK => 0);
  4328.     print $oldtmp $oldprofile;
  4329.     close($oldtmp);
  4330.  
  4331.     my $newtmp = new File::Temp(UNLINK => 0);
  4332.     print $newtmp $newprofile;
  4333.     close($newtmp);
  4334.  
  4335.     my $difftmp = new File::Temp(UNLINK => 0);
  4336.     my @diff;
  4337.     system("diff -u $oldtmp $newtmp > $difftmp");
  4338.     while (<$difftmp>) {
  4339.         push(@diff, $_) unless (($_ =~ /^(---|\+\+\+)/) ||
  4340.                                 ($_ =~ /^\@\@.*\@\@$/));
  4341.     }
  4342.     unlink($difftmp);
  4343.     unlink($oldtmp);
  4344.     unlink($newtmp);
  4345.     return join("", @diff);
  4346. }
  4347.  
  4348. sub display_changes {
  4349.     my ($oldprofile, $newprofile) = @_;
  4350.  
  4351.     my $oldtmp = new File::Temp( UNLINK => 0 );
  4352.     print $oldtmp $oldprofile;
  4353.     close($oldtmp);
  4354.  
  4355.     my $newtmp = new File::Temp( UNLINK => 0 );
  4356.     print $newtmp $newprofile;
  4357.     close($newtmp);
  4358.  
  4359.     my $difftmp = new File::Temp(UNLINK => 0);
  4360.     my @diff;
  4361.     system("diff -u $oldtmp $newtmp > $difftmp");
  4362.     if ($UI_Mode eq "yast") {
  4363.         while (<$difftmp>) {
  4364.             push(@diff, $_) unless (($_ =~ /^(---|\+\+\+)/) ||
  4365.                                     ($_ =~ /^\@\@.*\@\@$/));
  4366.         }
  4367.         UI_LongMessage(gettext("Profile Changes"), join("", @diff));
  4368.     } else {
  4369.         system("less $difftmp");
  4370.     }
  4371.  
  4372.     unlink($difftmp);
  4373.     unlink($oldtmp);
  4374.     unlink($newtmp);
  4375. }
  4376.  
  4377. sub setprocess ($$) {
  4378.     my ($pid, $profile) = @_;
  4379.  
  4380.     # don't do anything if the process exited already...
  4381.     return unless -e "/proc/$pid/attr/current";
  4382.  
  4383.     return unless open(CURR, "/proc/$pid/attr/current");
  4384.     my $current = <CURR>;
  4385.     return unless $current;
  4386.     chomp $current;
  4387.     close(CURR);
  4388.  
  4389.     # only change null profiles
  4390.     return unless $current =~ /null(-complain)*-profile/;
  4391.  
  4392.     return unless open(STAT, "/proc/$pid/stat");
  4393.     my $stat = <STAT>;
  4394.     chomp $stat;
  4395.     close(STAT);
  4396.  
  4397.     return unless $stat =~ /^\d+ \((\S+)\) /;
  4398.     my $currprog = $1;
  4399.  
  4400.     open(CURR, ">/proc/$pid/attr/current") or return;
  4401.     print CURR "setprofile $profile";
  4402.     close(CURR);
  4403. }
  4404.  
  4405. sub collapselog () {
  4406.     for my $sdmode (keys %prelog) {
  4407.         for my $profile (keys %{ $prelog{$sdmode} }) {
  4408.             for my $hat (keys %{ $prelog{$sdmode}{$profile} }) {
  4409.                 for my $path (keys %{ $prelog{$sdmode}{$profile}{$hat}{path} }) {
  4410.  
  4411.                     my $mode = $prelog{$sdmode}{$profile}{$hat}{path}{$path};
  4412.  
  4413.                     # we want to ignore anything from the log that's already
  4414.                     # in the profile
  4415.                     my $combinedmode = 0;
  4416.  
  4417.                     # is it in the original profile?
  4418.                     if ($sd{$profile}{$hat}{allow}{path}{$path}) {
  4419.                         $combinedmode |= $sd{$profile}{$hat}{allow}{path}{$path}{mode};
  4420.                     }
  4421.  
  4422.                     # does path match any regexps in original profile?
  4423.                     $combinedmode |= rematchfrag($sd{$profile}{$hat}, 'allow', $path);
  4424.  
  4425.                     # does path match anything pulled in by includes in
  4426.                     # original profile?
  4427.                     $combinedmode |= match_prof_incs_to_path($sd{$profile}{$hat}, 'allow', $path);
  4428.  
  4429.                     # if we found any matching entries, do the modes match?
  4430.                     unless ($combinedmode && mode_contains($combinedmode, $mode)) {
  4431.  
  4432.                         # merge in any previous modes from this run
  4433.                         if ($log{$sdmode}{$profile}{$hat}{$path}) {
  4434.                             $mode |= $log{$sdmode}{$profile}{$hat}{path}{$path};
  4435.                         }
  4436.  
  4437.                         # record the new entry
  4438.                         $log{$sdmode}{$profile}{$hat}{path}{$path} = $mode;
  4439.                     }
  4440.                 }
  4441.  
  4442.                 for my $capability (keys %{ $prelog{$sdmode}{$profile}{$hat}{capability} }) {
  4443.  
  4444.                     # if we don't already have this capability in the profile,
  4445.                     # add it
  4446.                     unless ($sd{$profile}{$hat}{allow}{capability}{$capability}{set}) {
  4447.                         $log{$sdmode}{$profile}{$hat}{capability}{$capability} = 1;
  4448.                     }
  4449.                 }
  4450.  
  4451.                 # Network toggle handling
  4452.                 my $ndref = $prelog{$sdmode}{$profile}{$hat}{netdomain};
  4453.                 for my $family ( keys %{$ndref} ) {
  4454.                     for my $sock_type ( keys %{$ndref->{$family}} ) {
  4455.                         unless ( profile_known_network($sd{$profile}{$hat},
  4456.                                $family, $sock_type)) {
  4457.                             $log{$sdmode}
  4458.                                 {$profile}
  4459.                                 {$hat}
  4460.                                 {netdomain}
  4461.                                 {$family}
  4462.                                 {$sock_type}=1;
  4463.                         }
  4464.                     }
  4465.                 }
  4466.             }
  4467.         }
  4468.     }
  4469. }
  4470.  
  4471. sub profilemode ($) {
  4472.     my $mode = shift;
  4473.  
  4474.     my $modifier = ($mode =~ m/[iupUP]/)[0];
  4475.     if ($modifier) {
  4476.         $mode =~ s/[iupUPx]//g;
  4477.         $mode .= $modifier . "x";
  4478.     }
  4479.  
  4480.     return $mode;
  4481. }
  4482.  
  4483. # kinky.
  4484. sub commonprefix (@) { (join("\0", @_) =~ m/^([^\0]*)[^\0]*(\0\1[^\0]*)*$/)[0] }
  4485. sub commonsuffix (@) { reverse(((reverse join("\0", @_)) =~ m/^([^\0]*)[^\0]*(\0\1[^\0]*)*$/)[0]); }
  4486.  
  4487. sub uniq (@) {
  4488.     my %seen;
  4489.     my @result = sort grep { !$seen{$_}++ } @_;
  4490.     return @result;
  4491. }
  4492.  
  4493. our $MODE_MAP_RE = "r|w|l|m|k|a|x|i|u|p|c|n|I|U|P|C|N";
  4494. our $LOG_MODE_RE = "r|w|l|m|k|a|x|ix|ux|px|cx|nx|pix|cix|Ix|Ux|Px|Cx|Nx|Pix|Cix";
  4495. our $PROFILE_MODE_RE = "r|w|l|m|k|a|ix|ux|px|cx|pix|cix|Ux|Px|Cx|Pix|Cix";
  4496. our $PROFILE_MODE_NT_RE = "r|w|l|m|k|a|x|ix|ux|px|cx|pix|cix|Ux|Px|Cx|Pix|Cix";
  4497. our $PROFILE_MODE_DENY_RE = "r|w|l|m|k|a|x";
  4498.  
  4499. sub split_log_mode($) {
  4500.     my $mode = shift;
  4501.     my $user = "";
  4502.     my $other = "";
  4503.  
  4504.     if ($mode =~ /(.*?)::(.*)/) {
  4505.     $user = $1 if ($1);
  4506.     $other = $2 if ($2);
  4507.     } else {
  4508.     $user = $mode;
  4509.     $other = $mode;
  4510.     }
  4511.     return ($user, $other);
  4512. }
  4513.  
  4514. sub map_log_mode ($) {
  4515.     my $mode = shift;
  4516.     return $mode;
  4517. #    $mode =~ s/(.*l.*)::.*/$1/ge;
  4518. #    $mode =~ s/.*::(.*l.*)/$1/ge;
  4519. #    $mode =~ s/:://;
  4520. #     return $mode;
  4521. #    return $1;
  4522. }
  4523.  
  4524. sub hide_log_mode($) {
  4525.     my $mode = shift;
  4526.  
  4527.     $mode =~ s/:://;
  4528.     return $mode;
  4529. }
  4530.  
  4531. sub validate_log_mode ($) {
  4532.     my $mode = shift;
  4533.  
  4534.     return ($mode =~ /^($LOG_MODE_RE)+$/) ? 1 : 0;
  4535. }
  4536.  
  4537. sub validate_profile_mode ($$$) {
  4538.     my ($mode, $allow, $nt_name) = @_;
  4539.  
  4540.     if ($allow eq 'deny') {
  4541.     return ($mode =~ /^($PROFILE_MODE_DENY_RE)+$/) ? 1 : 0;
  4542.     } elsif ($nt_name) {
  4543.     return ($mode =~ /^($PROFILE_MODE_NT_RE)+$/) ? 1 : 0;
  4544.     }
  4545.  
  4546.     return ($mode =~ /^($PROFILE_MODE_RE)+$/) ? 1 : 0;
  4547. }
  4548.  
  4549. # modes internally are stored as a bit Mask
  4550. sub sub_str_to_mode($) {
  4551.     my $str = shift;
  4552.     my $mode = 0;
  4553.  
  4554.     return 0 if (not $str);
  4555.  
  4556.     while ($str =~ s/(${MODE_MAP_RE})//) {
  4557.     my $tmp = $1;
  4558. #print "found mode $1\n";
  4559.  
  4560.     if ($tmp && $MODE_HASH{$tmp}) {
  4561.         $mode |= $MODE_HASH{$tmp};
  4562.     } else {
  4563. #print "found mode $tmp\n";
  4564.     }
  4565.     }
  4566.  
  4567. #my $tmp = mode_to_str($mode);
  4568. #print "parsed_mode $mode\n";
  4569.     return $mode;
  4570. }
  4571.  
  4572. sub print_mode ($) {
  4573.     my $mode = shift;
  4574.  
  4575.     my ($user, $other) = split_mode($mode);
  4576.  
  4577.     my $str = sub_str_to_mode($user) . "::" . sub_str_to_mode($other);
  4578.  
  4579.     return $str;
  4580. }
  4581.  
  4582. sub str_to_mode ($) {
  4583.     my $str = shift;
  4584.  
  4585.     return 0 if (not $str);
  4586.  
  4587.     my ($user, $other) = split_log_mode($str);
  4588.  
  4589. #print "str: $str  user: $user, other $other\n";
  4590.     # we only allow user or all
  4591.     $user = $other if (!$user);
  4592.  
  4593.     my $mode = sub_str_to_mode($user);
  4594.     $mode |= (sub_str_to_mode($other) << $AA_OTHER_SHIFT);
  4595.  
  4596. #print "user: $user " .sub_str_to_mode($user) . " other: $other " . (sub_str_to_mode($other) << $AA_OTHER_SHIFT) . " mode = $mode\n";
  4597.  
  4598.     return $mode;
  4599. }
  4600.  
  4601. sub log_str_to_mode($$$) {
  4602.     my ($profile, $str, $nt_name) = @_;
  4603.  
  4604.     my $mode = str_to_mode($str);
  4605.  
  4606.     # this will cover both nx and nix
  4607.     if (contains($mode, "Nx")) {
  4608.     # need to transform to px, cx
  4609.  
  4610.     if ($nt_name =~ /(.+?)\/\/(.+?)/) {
  4611.         my ($lprofile, $lhat) = @_;
  4612.         my $tmode = 0;
  4613.         if ($profile eq $profile) {
  4614.         if ($mode & ($AA_MAY_EXEC)) {
  4615.             $tmode = str_to_mode("Cx::");
  4616.         }
  4617.         if ($mode & ($AA_MAY_EXEC << $AA_OTHER_SHIFT)) {
  4618.             $tmode |= str_to_mode("Cx");
  4619.         }
  4620.         $nt_name = $lhat;
  4621.         } else {
  4622.         if ($mode & ($AA_MAY_EXEC)) {
  4623.             $tmode = str_to_mode("Px::");
  4624.         }
  4625.         if ($mode & ($AA_MAY_EXEC << $AA_OTHER_SHIFT)) {
  4626.             $tmode |= str_to_mode("Px");
  4627.         }
  4628.         $nt_name = $lhat;
  4629.         }
  4630.         $mode = ($mode & ~(str_to_mode("Nx")));
  4631.         $mode |= $tmode;
  4632.     }
  4633.     }
  4634.     return ($mode, $nt_name);
  4635. }
  4636.  
  4637. sub split_mode ($) {
  4638.     my $mode = shift;
  4639.  
  4640.     my $user = $mode & $AA_USER_MASK;
  4641.     my $other = ($mode >> $AA_OTHER_SHIFT) & $AA_USER_MASK;
  4642.  
  4643.     return ($user, $other);
  4644. }
  4645.  
  4646. sub is_user_mode ($) {
  4647.     my $mode = shift;
  4648.  
  4649.     my ($user, $other) = split_mode($mode);
  4650.  
  4651.     if ($user && !$other) {
  4652.     return 1;
  4653.     }
  4654.     return 0;
  4655. }
  4656.  
  4657. sub sub_mode_to_str($) {
  4658.     my $mode = shift;
  4659.     my $str = "";
  4660.  
  4661.     # "w" implies "a"
  4662.     $mode &= (~$AA_MAY_APPEND) if ($mode & $AA_MAY_WRITE);
  4663.     $str .= "m" if ($mode & $AA_EXEC_MMAP);
  4664.     $str .= "r" if ($mode & $AA_MAY_READ);
  4665.     $str .= "w" if ($mode & $AA_MAY_WRITE);
  4666.     $str .= "a" if ($mode & $AA_MAY_APPEND);
  4667.     $str .= "l" if ($mode & $AA_MAY_LINK);
  4668.     $str .= "k" if ($mode & $AA_MAY_LOCK);
  4669.     if ($mode & $AA_EXEC_UNCONFINED) {
  4670.     if ($mode & $AA_EXEC_UNSAFE) {
  4671.         $str .= "u";
  4672.     } else {
  4673.         $str .= "U";
  4674.     }
  4675.     }
  4676.     if ($mode & ($AA_EXEC_PROFILE | $AA_EXEC_NT)) {
  4677.     if ($mode & $AA_EXEC_UNSAFE) {
  4678.         $str .= "p";
  4679.     } else {
  4680.         $str .= "P";
  4681.     }
  4682.     }
  4683.     if ($mode & $AA_EXEC_CHILD) {
  4684.     if ($mode & $AA_EXEC_UNSAFE) {
  4685.         $str .= "c";
  4686.     } else {
  4687.         $str .= "C";
  4688.     }
  4689.     }
  4690.     $str .= "i" if ($mode & $AA_EXEC_INHERIT);
  4691.     $str .= "x" if ($mode & $AA_MAY_EXEC);
  4692.  
  4693.     return $str;
  4694. }
  4695.  
  4696. sub flatten_mode ($) {
  4697.     my $mode = shift;
  4698.  
  4699.     return 0 if (!$mode);
  4700.  
  4701.     $mode = ($mode & $AA_USER_MASK) | (($mode >> $AA_OTHER_SHIFT) & $AA_USER_MASK);
  4702.     $mode |= ($mode << $AA_OTHER_SHIFT);
  4703. }
  4704.  
  4705. sub mode_to_str ($) {
  4706.     my $mode = shift;
  4707.     $mode = flatten_mode($mode);
  4708.     return sub_mode_to_str($mode);
  4709. }
  4710.  
  4711. sub owner_flatten_mode($) {
  4712.     my $mode = shift;
  4713.     $mode = flatten_mode($mode) & $AA_USER_MASK;
  4714.     return $mode;
  4715. }
  4716.  
  4717. sub mode_to_str_user ($) {
  4718.     my $mode = shift;
  4719.  
  4720.     my ($user, $other) = split_mode($mode);
  4721.  
  4722.     my $str = "";
  4723.     $user = 0 if (!$user);
  4724.     $other = 0 if (!$other);
  4725.  
  4726.     if ($user & ~$other) {
  4727.     # more user perms than other
  4728.     $str = sub_mode_to_str($other). " + " if ($other);
  4729.     $str .= "owner " . sub_mode_to_str($user & ~$other);
  4730.     } elsif (is_user_mode($mode)) {
  4731.     $str = "owner " . sub_mode_to_str($user);
  4732.     } else {
  4733.     $str = sub_mode_to_str(flatten_mode($mode));
  4734.     }
  4735.     return $str;
  4736. }
  4737.  
  4738. sub mode_contains ($$) {
  4739.     my ($mode, $subset) = @_;
  4740.  
  4741.     # "w" implies "a"
  4742.     if ($mode & $AA_MAY_WRITE) {
  4743.     $mode |= $AA_MAY_APPEND;
  4744.     }
  4745.     if ($mode & ($AA_MAY_WRITE << $AA_OTHER_SHIFT)) {
  4746.     $mode |= ($AA_MAY_APPEND << $AA_OTHER_SHIFT);
  4747.     }
  4748.  
  4749.     # "?ix" implies "m"
  4750.     if ($mode & $AA_EXEC_INHERIT) {
  4751.     $mode |= $AA_EXEC_MMAP;
  4752.     }
  4753.     if ($mode & ($AA_EXEC_INHERIT << $AA_OTHER_SHIFT)) {
  4754.     $mode |= ($AA_EXEC_MMAP << $AA_OTHER_SHIFT);
  4755.     }
  4756.  
  4757.     return (($mode & $subset) == $subset);
  4758. }
  4759.  
  4760. sub contains ($$) {
  4761.     my ($mode, $str) = @_;
  4762.  
  4763.     return mode_contains($mode, str_to_mode($str));
  4764. }
  4765.  
  4766. # isSkippableFile - return true if filename matches something that
  4767. # should be skipped (rpm backup files, dotfiles, emacs backup files
  4768. # Annoyingly, this needs to be kept in sync with the skipped files
  4769. # in the apparmor initscript.
  4770. sub isSkippableFile($) {
  4771.     my $path = shift;
  4772.  
  4773.     return ($path =~ /(^|\/)\.[^\/]*$/
  4774.             || $path =~ /\.rpm(save|new)$/
  4775.             || $path =~ /\.dpkg-(old|new)$/
  4776.         || $path =~ /\.swp$/
  4777.             || -e "$profiledir/disable/$path"
  4778.             || -e "$profiledir/force-complain/$path"
  4779.             || $path =~ /\~$/);
  4780. }
  4781.  
  4782. sub checkIncludeSyntax($) {
  4783.     my $errors = shift;
  4784.  
  4785.     if (opendir(SDDIR, $profiledir)) {
  4786.         my @incdirs = grep { (!/^\./) && (-d "$profiledir/$_") } readdir(SDDIR);
  4787.         close(SDDIR);
  4788.         while (my $id = shift @incdirs) {
  4789.             if (opendir(SDDIR, "$profiledir/$id")) {
  4790.                 for my $path (grep { !/^\./ } readdir(SDDIR)) {
  4791.                     chomp($path);
  4792.                     next if isSkippableFile($path);
  4793.                     if (-f "$profiledir/$id/$path") {
  4794.                         my $file = "$id/$path";
  4795.                         $file =~ s/$profiledir\///;
  4796.                         eval { loadinclude($file); };
  4797.                         if ( defined $@ && $@ ne "" ) {
  4798.                             push @$errors, $@;
  4799.                         }
  4800.                     } elsif (-d "$id/$path") {
  4801.                         push @incdirs, "$id/$path";
  4802.                     }
  4803.                 }
  4804.                 closedir(SDDIR);
  4805.             }
  4806.         }
  4807.     }
  4808.     return $errors;
  4809. }
  4810.  
  4811. sub checkProfileSyntax ($) {
  4812.     my $errors = shift;
  4813.  
  4814.     # Check the syntax of profiles
  4815.  
  4816.     opendir(SDDIR, $profiledir)
  4817.       or fatal_error "Can't read AppArmor profiles in $profiledir.";
  4818.     for my $file (grep { -f "$profiledir/$_" } readdir(SDDIR)) {
  4819.         next if isSkippableFile($file);
  4820.         my $err = readprofile("$profiledir/$file", \&printMessageErrorHandler, 1);
  4821.         if (defined $err and $err ne "") {
  4822.             push @$errors, $err;
  4823.         }
  4824.     }
  4825.     closedir(SDDIR);
  4826.     return $errors;
  4827. }
  4828.  
  4829. sub printMessageErrorHandler ($) {
  4830.     my $message = shift;
  4831.     return $message;
  4832. }
  4833.  
  4834. sub readprofiles () {
  4835.     opendir(SDDIR, $profiledir)
  4836.       or fatal_error "Can't read AppArmor profiles in $profiledir.";
  4837.     for my $file (grep { -f "$profiledir/$_" } readdir(SDDIR)) {
  4838.         next if isSkippableFile($file);
  4839.         readprofile("$profiledir/$file", \&fatal_error, 1);
  4840.     }
  4841.     closedir(SDDIR);
  4842. }
  4843.  
  4844. sub readinactiveprofiles () {
  4845.     return if ( ! -e $extraprofiledir );
  4846.     opendir(ESDDIR, $extraprofiledir) or
  4847.       fatal_error "Can't read AppArmor profiles in $extraprofiledir.";
  4848.     for my $file (grep { -f "$extraprofiledir/$_" } readdir(ESDDIR)) {
  4849.         next if $file =~ /\.rpm(save|new)|README$/;
  4850.         readprofile("$extraprofiledir/$file", \&fatal_error, 0);
  4851.     }
  4852.     closedir(ESDDIR);
  4853. }
  4854.  
  4855. sub readprofile ($$$) {
  4856.     my $file          = shift;
  4857.     my $error_handler = shift;
  4858.     my $active_profile = shift;
  4859.     if (open(SDPROF, "$file")) {
  4860.         local $/;
  4861.         my $data = <SDPROF>;
  4862.         close(SDPROF);
  4863.  
  4864.         eval {
  4865.             my $profile_data = parse_profile_data($data, $file, 0);
  4866.             if ($profile_data && $active_profile) {
  4867.                 attach_profile_data(\%sd, $profile_data);
  4868.                 attach_profile_data(\%original_sd, $profile_data);
  4869.             } elsif ( $profile_data ) {
  4870.                 attach_profile_data(\%extras,      $profile_data);
  4871.             }
  4872.         };
  4873.  
  4874.         # if there were errors loading the profile, call the error handler
  4875.         if ($@) {
  4876.             $@ =~ s/\n$//;
  4877.             return &$error_handler($@);
  4878.         }
  4879.     } else {
  4880.         $DEBUGGING && debug "readprofile: can't read $file - skipping";
  4881.     }
  4882. }
  4883.  
  4884. sub attach_profile_data {
  4885.     my ($profiles, $profile_data) = @_;
  4886.  
  4887.     # make deep copies of the profile data so that if we change one set of
  4888.     # profile data, we're not changing others because of sharing references
  4889.     for my $p ( keys %$profile_data) {
  4890.           $profiles->{$p} = dclone($profile_data->{$p});
  4891.     }
  4892. }
  4893.  
  4894. sub parse_profile_data {
  4895.     my ($data, $file, $do_include) = @_;
  4896.  
  4897.  
  4898.     my ($profile_data, $profile, $hat, $in_contained_hat, $repo_data,
  4899.         @parsed_profiles);
  4900.     my $initial_comment = "";
  4901.  
  4902.     if ($do_include) {
  4903.     $profile = $file;
  4904.     $hat = $file;
  4905.     }
  4906.  
  4907.     for (split(/\n/, $data)) {
  4908.         chomp;
  4909.  
  4910.         # we don't care about blank lines
  4911.         next if /^\s*$/;
  4912.  
  4913.         # start of a profile...
  4914.         if (m/^\s*(("??\/.+?"??)|(profile\s+("??.+?"??)))\s+((flags=)?\((.+)\)\s+)*\{\s*(#.*)?$/) {
  4915.         if ($do_include) {
  4916.         die "include <$file> contains syntax errors.\n";
  4917.         }
  4918.  
  4919.             # if we run into the start of a profile while we're already in a
  4920.             # profile, something's wrong...
  4921.             if ($profile) {
  4922.         unless (($profile eq $hat) and $4) {
  4923.             die "$profile profile in $file contains syntax errors.\n";
  4924.         }
  4925.         }
  4926.  
  4927.             # we hit the start of a profile, keep track of it...
  4928.         if ($profile && ($profile eq $hat) && $4) {
  4929.         # local profile
  4930.         $hat = $4;
  4931.         $in_contained_hat = 1;
  4932.         $profile_data->{$profile}{$hat}{profile} = 1;
  4933.         } else {
  4934.         $profile  = $2 || $4;
  4935.         # hat is same as profile name if we're not in a hat
  4936.         ($profile, $hat) = split /\/\//, $profile;
  4937.         $in_contained_hat = 0;
  4938.         if ($hat) {
  4939.             $profile_data->{$profile}{$hat}{external} = 1;
  4940.         }
  4941.  
  4942.         $hat ||= $profile;
  4943.         }
  4944.  
  4945.             my $flags = $7;
  4946.  
  4947.             # deal with whitespace in profile and hat names.
  4948.             $profile = strip_quotes($profile);
  4949.             $hat     = strip_quotes($hat) if $hat;
  4950.  
  4951.         # save off the name and filename
  4952.         $profile_data->{$profile}{$hat}{name} = $profile;
  4953.         $profile_data->{$profile}{$hat}{filename} = $file;
  4954.         $filelist{$file}{profiles}{$profile}{$hat} = 1;
  4955.  
  4956.             # keep track of profile flags
  4957.         $profile_data->{$profile}{$hat}{flags} = $flags;
  4958.  
  4959.             $profile_data->{$profile}{$hat}{allow}{netdomain} = { };
  4960.             $profile_data->{$profile}{$hat}{allow}{path} = { };
  4961.  
  4962.             # store off initial comment if they have one
  4963.             $profile_data->{$profile}{$hat}{initial_comment} = $initial_comment
  4964.               if $initial_comment;
  4965.             $initial_comment = "";
  4966.  
  4967.             if ($repo_data) {
  4968.                 $profile_data->{$profile}{$profile}{repo}{url}  = $repo_data->{url};
  4969.                 $profile_data->{$profile}{$profile}{repo}{user} = $repo_data->{user};
  4970.                 $profile_data->{$profile}{$profile}{repo}{id}   = $repo_data->{id};
  4971.                 $repo_data = undef;
  4972.             }
  4973.  
  4974.         } elsif (m/^\s*\}\s*(#.*)?$/) { # end of a profile...
  4975.  
  4976.             # if we hit the end of a profile when we're not in one, something's
  4977.             # wrong...
  4978.         if ($do_include) {
  4979.         die "include <$file> contains syntax errors.";
  4980.         }
  4981.             if (not $profile) {
  4982.                 die sprintf(gettext('%s contains syntax errors.'), $file) . "\n";
  4983.             }
  4984.  
  4985.             if ($in_contained_hat) {
  4986.                 $hat = $profile;
  4987.                 $in_contained_hat = 0;
  4988.             } else {
  4989.                 push @parsed_profiles, $profile;
  4990.                 # mark that we're outside of a profile now...
  4991.                 $profile = undef;
  4992.             }
  4993.  
  4994.             $initial_comment = "";
  4995.  
  4996.         } elsif (m/^\s*(audit\s+)?(deny\s+)?capability\s+(\S+)\s*,\s*(#.*)?$/) {  # capability entry
  4997.             if (not $profile) {
  4998.                 die sprintf(gettext('%s contains syntax errors.'), $file) . "\n";
  4999.             }
  5000.  
  5001.         my $audit = $1 ? 1 : 0;
  5002.         my $allow = $2 ? 'deny' : 'allow';
  5003.         $allow = 'deny' if ($2);
  5004.             my $capability = $3;
  5005.             $profile_data->{$profile}{$hat}{$allow}{capability}{$capability}{set} = 1;
  5006.             $profile_data->{$profile}{$hat}{$allow}{capability}{$capability}{audit} = $audit;
  5007.         } elsif (m/^\s*set capability\s+(\S+)\s*,\s*(#.*)?$/) {  # capability entry
  5008.             if (not $profile) {
  5009.                 die sprintf(gettext('%s contains syntax errors.'), $file) . "\n";
  5010.             }
  5011.  
  5012.             my $capability = $1;
  5013.             $profile_data->{$profile}{$hat}{set_capability}{$capability} = 1;
  5014.  
  5015.     } elsif (m/^\s*(audit\s+)?(deny\s+)?link\s+(((subset)|(<=))\s+)?([\"\@\/].*?"??)\s+->\s*([\"\@\/].*?"??)\s*,\s*(#.*)?$/) { # for now just keep link
  5016.             if (not $profile) {
  5017.                 die sprintf(gettext('%s contains syntax errors.'), $file) . "\n";
  5018.             }
  5019.         my $audit = $1 ? 1 : 0;
  5020.         my $allow = $2 ? 'deny' : 'allow';
  5021.  
  5022.         my $subset = $4;
  5023.             my $link = strip_quotes($7);
  5024.         my $value = strip_quotes($8);
  5025.         $profile_data->{$profile}{$hat}{$allow}{link}{$link}{to} = $value;
  5026.         $profile_data->{$profile}{$hat}{$allow}{link}{$link}{mode} = $AA_MAY_LINK;
  5027.         if ($subset) {
  5028.         $profile_data->{$profile}{$hat}{$allow}{link}{$link}{mode} = $AA_LINK_SUBSET;
  5029.         }
  5030.         if ($audit) {
  5031.         $profile_data->{$profile}{$hat}{$allow}{link}{$link}{audit} = $AA_LINK_SUBSET;
  5032.         } else {
  5033.         $profile_data->{$profile}{$hat}{$allow}{link}{$link}{audit} = 0;
  5034.         }
  5035.  
  5036.     } elsif (m/^\s*change_profile\s+->\s*("??.+?"??),(#.*)?$/) { # for now just keep change_profile
  5037.             if (not $profile) {
  5038.                 die sprintf(gettext('%s contains syntax errors.'), $file) . "\n";
  5039.             }
  5040.             my $cp = strip_quotes($1);
  5041.  
  5042.             $profile_data->{$profile}{$hat}{change_profile}{$cp} = 1;
  5043.     } elsif (m/^\s*alias\s+("??.+?"??)\s+->\s*("??.+?"??)\s*,(#.*)?$/) { # never do anything with aliases just keep them
  5044.             my $from = strip_quotes($1);
  5045.         my $to = strip_quotes($2);
  5046.  
  5047.             if ($profile) {
  5048.         $profile_data->{$profile}{$hat}{alias}{$from} = $to;
  5049.         } else {
  5050.         unless (exists $filelist{$file}) {
  5051.             $filelist{$file} = { };
  5052.         }
  5053.         $filelist{$file}{alias}{$from} = $to;
  5054.         }
  5055.  
  5056.        } elsif (m/^\s*set\s+rlimit\s+(.+)\s+<=\s*(.+)\s*,(#.*)?$/) { # never do anything with rlimits just keep them
  5057.        if (not $profile) {
  5058.            die sprintf(gettext('%s contains syntax errors.'), $file) . "\n";
  5059.        }
  5060.        my $from = $1;
  5061.            my $to = $2;
  5062.  
  5063.        $profile_data->{$profile}{$hat}{rlimit}{$from} = $to;
  5064.  
  5065.         } elsif (/^\s*(\$\{?[[:alpha:]][[:alnum:]_]*\}?)\s*=\s*(true|false)\s*,?\s*(#.*)?$/i) { # boolean definition
  5066.        if (not $profile) {
  5067.            die sprintf(gettext('%s contains syntax errors.'), $file) . "\n";
  5068.        }
  5069.        my $bool_var = $1;
  5070.            my $value = $2;
  5071.  
  5072.        $profile_data->{$profile}{$hat}{lvar}{$bool_var} = $value;
  5073.         } elsif (/^\s*(@\{?[[:alpha:]][[:alnum:]_]+\}?)\s*\+?=\s*(.+?)\s*,?\s*(#.*)?$/) { # variable additions both += and = doesn't mater
  5074.        my $list_var = strip_quotes($1);
  5075.            my $value = strip_quotes($2);
  5076.  
  5077.        if ($profile) {
  5078.            unless (exists $profile_data->{$profile}{$hat}{lvar}) {
  5079.            # create lval hash by sticking an empty list into list_var
  5080.            my @empty = ();
  5081.            $profile_data->{$profile}{$hat}{lvar}{$list_var} = \@empty;
  5082.            }
  5083.  
  5084.            store_list_var($profile_data->{$profile}{$hat}{lvar}, $list_var, $value);
  5085.        } else  {
  5086.            unless (exists $filelist{$file}{lvar}) {
  5087.            # create lval hash by sticking an empty list into list_var
  5088.            my @empty = ();
  5089.            $filelist{$file}{lvar}{$list_var} = \@empty;
  5090.            }
  5091.  
  5092.            store_list_var($filelist{$file}{lvar}, $list_var, $value);
  5093.        }
  5094.         } elsif (m/^\s*if\s+(not\s+)?(\$\{?[[:alpha:]][[:alnum:]_]*\}?)\s*\{\s*(#.*)?$/) { # conditional -- boolean
  5095.         } elsif (m/^\s*if\s+(not\s+)?defined\s+(@\{?[[:alpha:]][[:alnum:]_]+\}?)\s*\{\s*(#.*)?$/) { # conditional -- variable defined
  5096.         } elsif (m/^\s*if\s+(not\s+)?defined\s+(\$\{?[[:alpha:]][[:alnum:]_]+\}?)\s*\{\s*(#.*)?$/) { # conditional -- boolean defined
  5097.         } elsif (m/^\s*(audit\s+)?(deny\s+)?(owner\s+)?([\"\@\/].*?)\s+(\S+)(\s+->\s*(.*?))?\s*,\s*(#.*)?$/) {     # path entry
  5098.             if (not $profile) {
  5099.                 die sprintf(gettext('%s contains syntax errors.'), $file) . "\n";
  5100.             }
  5101.  
  5102.         my $audit = $1 ? 1 : 0;
  5103.         my $allow = $2 ? 'deny' : 'allow';
  5104.         my $user = $3 ? 1 : 0;
  5105.             my ($path, $mode, $nt_name) = ($4, $5, $7);
  5106.  
  5107.             # strip off any trailing spaces.
  5108.             $path =~ s/\s+$//;
  5109.             $nt_name =~ s/\s+$// if $nt_name;
  5110.  
  5111.             $path = strip_quotes($path);
  5112.             $nt_name = strip_quotes($nt_name) if $nt_name;
  5113.  
  5114.             # make sure they don't have broken regexps in the profile
  5115.             my $p_re = convert_regexp($path);
  5116.             eval { "foo" =~ m/^$p_re$/; };
  5117.             if ($@) {
  5118.                 die sprintf(gettext('Profile %s contains invalid regexp %s.'),
  5119.                                      $file, $path) . "\n";
  5120.             }
  5121.  
  5122.             if (!validate_profile_mode($mode, $allow, $nt_name)) {
  5123.                 fatal_error(sprintf(gettext('Profile %s contains invalid mode %s.'), $file, $mode));
  5124.             }
  5125.  
  5126.         my $tmpmode;
  5127.         if ($user) {
  5128.         $tmpmode = str_to_mode("${mode}::");
  5129.         } else {
  5130.         $tmpmode = str_to_mode($mode);
  5131.         }
  5132.             $profile_data->{$profile}{$hat}{$allow}{path}{$path}{mode} = $tmpmode;
  5133.             $profile_data->{$profile}{$hat}{$allow}{path}{$path}{to} = $nt_name if $nt_name;
  5134.         if ($audit) {
  5135.         $profile_data->{$profile}{$hat}{$allow}{path}{$path}{audit} = $tmpmode;
  5136.         } else {
  5137.         $profile_data->{$profile}{$hat}{$allow}{path}{$path}{audit} = 0;
  5138.         }
  5139.         } elsif (m/^\s*#include <(.+)>\s*$/) {     # include stuff
  5140.             my $include = $1;
  5141.  
  5142.             if ($profile) {
  5143.                 $profile_data->{$profile}{$hat}{include}{$include} = 1;
  5144.             } else {
  5145.                 unless (exists $filelist{$file}) {
  5146.                    $filelist{$file} = { };
  5147.                 }
  5148.                 $filelist{$file}{include}{$include} = 1;
  5149.             }
  5150.  
  5151.             # try to load the include...
  5152.             my $ret = eval { loadinclude($include); };
  5153.             # propagate errors up the chain
  5154.             if ($@) { die $@; }
  5155.  
  5156.             return $ret if ( $ret != 0 );
  5157.  
  5158.         } elsif (/^\s*(audit\s+)?(deny\s+)?network(.*)/) {
  5159.             if (not $profile) {
  5160.                 die sprintf(gettext('%s contains syntax errors.'), $file) . "\n";
  5161.             }
  5162.         my $audit = $1 ? 1 : 0;
  5163.         my $allow = $2 ? 'deny' : 'allow';
  5164.         my $network = $3;
  5165.  
  5166.             unless ($profile_data->{$profile}{$hat}{$allow}{netdomain}{rule}) {
  5167.                 $profile_data->{$profile}{$hat}{$allow}{netdomain}{rule} = { };
  5168.             }
  5169.  
  5170.             if ($network =~ /\s+(\S+)\s+(\S+)\s*,\s*(#.*)?$/ ) {
  5171.         my $fam = $1;
  5172.         my $type = $2;
  5173.                 $profile_data->{$profile}{$hat}{$allow}{netdomain}{rule}{$fam}{$type} = 1;
  5174.                 $profile_data->{$profile}{$hat}{$allow}{netdomain}{audit}{$fam}{$type} = $audit;
  5175.             } elsif ( $network =~ /\s+(\S+)\s*,\s*(#.*)?$/ ) {
  5176.         my $fam = $1;
  5177.                 $profile_data->{$profile}{$hat}{$allow}{netdomain}{rule}{$fam} = 1;
  5178.         $profile_data->{$profile}{$hat}{$allow}{netdomain}{audit}{$fam} = $audit;
  5179.             } else {
  5180.                 $profile_data->{$profile}{$hat}{$allow}{netdomain}{rule}{all} = 1;
  5181.                 $profile_data->{$profile}{$hat}{$allow}{netdomain}{audit}{all} = 1;
  5182.             }
  5183.         } elsif (/^\s*(tcp_connect|tcp_accept|udp_send|udp_receive)/) {
  5184. # just ignore and drop old style network
  5185. #        die sprintf(gettext('%s contains old style network rules.'), $file) . "\n";
  5186.  
  5187.         } elsif (m/^\s*\^(\"??.+?\"??)\s*,\s*(#.*)?$/) {
  5188.         if (not $profile) {
  5189.         die "$file contains syntax errors.";
  5190.         }
  5191.         # change_hat declaration - needed to change_hat to an external
  5192.         # hat
  5193.             $hat = $1;
  5194.             $hat = $1 if $hat =~ /^"(.+)"$/;
  5195.  
  5196.         #store we have a declaration if the hat hasn't been seen
  5197.         $profile_data->{$profile}{$hat}{'declared'} = 1
  5198.         unless exists($profile_data->{$profile}{$hat}{declared});
  5199.  
  5200.         } elsif (m/^\s*\^(\"??.+?\"??)\s+(flags=\(.+\)\s+)*\{\s*(#.*)?$/) {
  5201.         if ($do_include) {
  5202.         die "include <$file> contains syntax errors.";
  5203.         }
  5204.             # start of embedded hat syntax hat definition
  5205.             # read in and mark as changed so that will be written out in the new
  5206.             # format
  5207.  
  5208.             # if we hit the start of a contained hat when we're not in a profile
  5209.             # something is wrong...
  5210.             if (not $profile) {
  5211.                 die sprintf(gettext('%s contains syntax errors.'), $file) . "\n";
  5212.             }
  5213.  
  5214.             $in_contained_hat = 1;
  5215.  
  5216.             # we hit the start of a hat inside the current profile
  5217.             $hat = $1;
  5218.             my $flags = $3;
  5219.  
  5220.             # strip quotes.
  5221.             $hat = $1 if $hat =~ /^"(.+)"$/;
  5222.  
  5223.             # keep track of profile flags
  5224.         $profile_data->{$profile}{$hat}{flags} = $flags;
  5225.  
  5226.         # we have seen more than a declaration so clear it
  5227.         $profile_data->{$profile}{$hat}{'declared'} = 0;
  5228.             $profile_data->{$profile}{$hat}{allow}{path} = { };
  5229.             $profile_data->{$profile}{$hat}{allow}{netdomain} = { };
  5230.  
  5231.             # store off initial comment if they have one
  5232.             $profile_data->{$profile}{$hat}{initial_comment} = $initial_comment
  5233.               if $initial_comment;
  5234.             $initial_comment = "";
  5235.             #don't mark profile as changed just because it has an embedded
  5236.         #hat.
  5237.             #$changed{$profile} = 1;
  5238.  
  5239.         $filelist{$file}{profiles}{$profile}{$hat} = 1;
  5240.  
  5241.         } elsif (/^\s*\#/) {
  5242.             # we only currently handle initial comments
  5243.             if (not $profile) {
  5244.                 # ignore vim syntax highlighting lines
  5245.                 next if /^\s*\# vim:syntax/;
  5246.                 # ignore Last Modified: lines
  5247.                 next if /^\s*\# Last Modified:/;
  5248.                 if (/^\s*\# REPOSITORY: (\S+) (\S+) (\S+)$/) {
  5249.                     $repo_data = { url => $1, user => $2, id => $3 };
  5250.                 } elsif (/^\s*\# REPOSITORY: NEVERSUBMIT$/) {
  5251.                     $repo_data = { neversubmit => 1 };
  5252.                 } else {
  5253.                   $initial_comment .= "$_\n";
  5254.                 }
  5255.             }
  5256.         } else {
  5257.         # we hit something we don't understand in a profile...
  5258.         die sprintf(gettext('%s contains syntax errors. Line [%s]'), $file, $_) . "\n";
  5259.         }
  5260.     }
  5261.  
  5262.     #
  5263.     # Cleanup : add required hats if not present in the
  5264.     #           parsed profiles
  5265.     #
  5266. if (not $do_include) {
  5267.     for my $hatglob (keys %{$cfg->{required_hats}}) {
  5268.         for my $parsed_profile  ( sort @parsed_profiles )  {
  5269.             if ($parsed_profile =~ /$hatglob/) {
  5270.                 for my $hat (split(/\s+/, $cfg->{required_hats}{$hatglob})) {
  5271.                     unless ($profile_data->{$parsed_profile}{$hat}) {
  5272.                         $profile_data->{$parsed_profile}{$hat} = { };
  5273.                     }
  5274.                 }
  5275.             }
  5276.         }
  5277.     }
  5278.  
  5279. }    # if we're still in a profile when we hit the end of the file, it's bad
  5280.     if ($profile and not $do_include) {
  5281.         die "Reached the end of $file while we were still inside the $profile profile.\n";
  5282.     }
  5283.  
  5284.     return $profile_data;
  5285. }
  5286.  
  5287. sub eliminate_duplicates(@) {
  5288.     my @data =@_;
  5289.  
  5290.     my %set = map { $_ => 1 } @_;
  5291.     @data = keys %set;
  5292.  
  5293.     return @data;
  5294. }
  5295.  
  5296. sub separate_vars($) {
  5297.     my $vs = shift;
  5298.     my @data;
  5299.  
  5300. #    while ($vs =~ /\s*(((\"([^\"]|\\\"))+?\")|\S*)\s*(.*)$/) {
  5301.     while ($vs =~ /\s*((\".+?\")|([^\"]\S+))\s*(.*)$/) {
  5302.     my $tmp = $1;
  5303.     push @data, strip_quotes($tmp);
  5304.     $vs = $4;
  5305.     }
  5306.  
  5307.     return @data;
  5308. }
  5309.  
  5310. sub is_active_profile ($) {
  5311.     my $pname = shift;
  5312.     if ( $sd{$pname} ) {
  5313.         return 1;
  5314.     }  else {
  5315.         return 0;
  5316.     }
  5317. }
  5318.  
  5319. sub store_list_var (\%$$) {
  5320.     my ($vars, $list_var, $value) = @_;
  5321.  
  5322.     my @vlist = (separate_vars($value));
  5323.  
  5324. #       if (exists $profile_data->{$profile}{$hat}{lvar}{$list_var}) {
  5325. #           @vlist = (@vlist, @{$profile_data->{$profile}{$hat}{lvar}{$list_var}});
  5326. #       }
  5327. #
  5328. #       @vlist = eliminate_duplicates(@vlist);
  5329. #       $profile_data->{$profile}{$hat}{lvar}{$list_var} = \@vlist;
  5330.  
  5331.     if (exists $vars->{$list_var}) {
  5332.     @vlist = (@vlist, @{$vars->{$list_var}});
  5333.     }
  5334.  
  5335.     @vlist = eliminate_duplicates(@vlist);
  5336.     $vars->{$list_var} = \@vlist;
  5337.  
  5338.  
  5339. }
  5340.  
  5341. sub strip_quotes ($) {
  5342.     my $data = shift;
  5343.     $data = $1 if $data =~ /^\"(.*)\"$/;
  5344.     return $data;
  5345. }
  5346.  
  5347. sub quote_if_needed ($) {
  5348.     my $data = shift;
  5349.     $data = "\"$data\"" if $data =~ /\s/;
  5350.  
  5351.     return $data;
  5352. }
  5353.  
  5354. sub escape ($) {
  5355.     my $dangerous = shift;
  5356.  
  5357.     $dangerous = strip_quotes($dangerous);
  5358.  
  5359.     $dangerous =~ s/((?<!\\))"/$1\\"/g;
  5360.     if ($dangerous =~ m/(\s|^$|")/) {
  5361.         $dangerous = "\"$dangerous\"";
  5362.     }
  5363.  
  5364.     return $dangerous;
  5365. }
  5366.  
  5367. sub writeheader ($$$$$) {
  5368.     my ($profile_data, $depth, $name, $embedded_hat, $write_flags) = @_;
  5369.  
  5370.     my $pre = '  ' x $depth;
  5371.     my @data;
  5372.     # deal with whitespace in profile names...
  5373.     $name = quote_if_needed($name);
  5374.  
  5375.     $name = "profile $name" if ((!$embedded_hat && $name =~ /^[^\/]|^"[^\/]/)
  5376.                 || ($embedded_hat && $name =~/^[^^]/));
  5377.  
  5378.     #push @data, "#include <tunables/global>" unless ( $is_hat );
  5379.     if ($write_flags and  $profile_data->{flags}) {
  5380.         push @data, "${pre}$name flags=($profile_data->{flags}) {";
  5381.     } else {
  5382.         push @data, "${pre}$name {";
  5383.     }
  5384.  
  5385.     return @data;
  5386. }
  5387.  
  5388. sub qin_trans ($) {
  5389.     my $value = shift;
  5390.     return quote_if_needed($value);
  5391. }
  5392.  
  5393. sub write_single ($$$$$$) {
  5394.     my ($profile_data, $depth, $allow, $name, $prefix, $tail) = @_;
  5395.     my $ref;
  5396.     my @data;
  5397.  
  5398.     if ($allow) {
  5399.     $ref = $profile_data->{$allow};
  5400.     if ($allow eq 'deny') {
  5401.         $allow .= " ";
  5402.     } else {
  5403.         $allow = "";
  5404.     }
  5405.     } else {
  5406.     $ref = $profile_data;
  5407.     $allow = "";
  5408.     }
  5409.  
  5410.     my $pre = "  " x $depth;
  5411.  
  5412.  
  5413.     # dump out the data
  5414.     if (exists $ref->{$name}) {
  5415.         for my $key (sort keys %{$ref->{$name}}) {
  5416.         my $qkey = quote_if_needed($key);
  5417.         push @data, "${pre}${allow}${prefix}${qkey}${tail}";
  5418.         }
  5419.         push @data, "" if keys %{$ref->{$name}};
  5420.     }
  5421.  
  5422.     return @data;
  5423. }
  5424.  
  5425. sub write_pair ($$$$$$$$) {
  5426.     my ($profile_data, $depth, $allow, $name, $prefix, $sep, $tail, $fn) = @_;
  5427.     my $ref;
  5428.     my @data;
  5429.  
  5430.     if ($allow) {
  5431.     $ref = $profile_data->{$allow};
  5432.     if ($allow eq 'deny') {
  5433.         $allow .= " ";
  5434.     } else {
  5435.         $allow = "";
  5436.     }
  5437.     } else {
  5438.     $ref = $profile_data;
  5439.     $allow = "";
  5440.     }
  5441.  
  5442.     my $pre = "  " x $depth;
  5443.  
  5444.     # dump out the data
  5445.     if (exists $ref->{$name}) {
  5446.         for my $key (sort keys %{$ref->{$name}}) {
  5447.         my $value = &{$fn}($ref->{$name}{$key});
  5448.             push @data, "${pre}${allow}${prefix}${key}${sep}${value}${tail}";
  5449.         }
  5450.         push @data, "" if keys %{$ref->{$name}};
  5451.     }
  5452.  
  5453.     return @data;
  5454. }
  5455.  
  5456. sub writeincludes ($$) {
  5457.     my ($prof_data, $depth) = @_;
  5458.  
  5459.     return write_single($prof_data, $depth,'', 'include', "#include <", ">");
  5460. }
  5461.  
  5462. sub writechange_profile ($$) {
  5463.     my ($prof_data, $depth) = @_;
  5464.  
  5465.     return write_single($prof_data, $depth, '', 'change_profile', "change_profile -> ", ",");
  5466. }
  5467.  
  5468. sub writealiases ($$) {
  5469.     my ($prof_data, $depth) = @_;
  5470.  
  5471.     return write_pair($prof_data, $depth, '', 'alias', "alias ", " -> ", ",", \&qin_trans);
  5472. }
  5473.  
  5474. sub writerlimits ($$) {
  5475.     my ($prof_data, $depth) = @_;
  5476.  
  5477.     return write_pair($prof_data, $depth, '', 'rlimit', "set rlimit ", " <= ", ",", \&qin_trans);
  5478. }
  5479.  
  5480. # take a list references and process it
  5481. sub var_transform($) {
  5482.     my $ref = shift;
  5483.     my @in = @{$ref};
  5484.     my @data;
  5485.  
  5486.     foreach my $value (@in) {
  5487.     push @data, quote_if_needed($value);
  5488.     }
  5489.  
  5490.     return join " ", @data;
  5491. }
  5492.  
  5493. sub writelistvars ($$) {
  5494.     my ($prof_data, $depth) = @_;
  5495.  
  5496.     return write_pair($prof_data, $depth, '', 'lvar', "", " = ", ",", \&var_transform);
  5497. }
  5498.  
  5499. sub writecap_rules ($$$) {
  5500.     my ($profile_data, $depth, $allow) = @_;
  5501.  
  5502.     my $allowstr = $allow eq 'deny' ? 'deny ' : '';
  5503.     my $pre = "  " x $depth;
  5504.  
  5505.     my @data;
  5506.     if (exists $profile_data->{$allow}{capability}) {
  5507.         for my $cap (sort keys %{$profile_data->{$allow}{capability}}) {
  5508.         my $audit = ($profile_data->{$allow}{capability}{$cap}{audit}) ? 'audit ' : '';
  5509.         if ($profile_data->{$allow}{capability}{$cap}{set}) {
  5510.         push @data, "${pre}${audit}${allowstr}capability ${cap},";
  5511.         }
  5512.         }
  5513.     push @data, "";
  5514.     }
  5515.  
  5516.     return @data;
  5517. }
  5518.  
  5519. sub writecapabilities ($$) {
  5520.     my ($prof_data, $depth) = @_;
  5521.     my @data;
  5522.     push @data, write_single($prof_data, $depth, '', 'set_capability', "set capability ", ",");
  5523.     push @data, writecap_rules($prof_data, $depth, 'deny');
  5524.     push @data, writecap_rules($prof_data, $depth, 'allow');
  5525.     return @data;
  5526. }
  5527.  
  5528. sub writenet_rules ($$$) {
  5529.     my ($profile_data, $depth, $allow) = @_;
  5530.  
  5531.     my $allowstr = $allow eq 'deny' ? 'deny ' : '';
  5532.  
  5533.     my $pre = "  " x $depth;
  5534.     my $audit = "";
  5535.  
  5536.     my @data;
  5537.     # dump out the netdomain entries...
  5538.     if (exists $profile_data->{$allow}{netdomain}) {
  5539.         if ( $profile_data->{$allow}{netdomain}{rule} &&
  5540.              $profile_data->{$allow}{netdomain}{rule} eq 'all') {
  5541.         $audit = "audit " if $profile_data->{$allow}{netdomain}{audit}{all};
  5542.             push @data, "${pre}${audit}network,";
  5543.         } else {
  5544.             for my $fam (sort keys %{$profile_data->{$allow}{netdomain}{rule}}) {
  5545.                 if ( $profile_data->{$allow}{netdomain}{rule}{$fam} == 1 ) {
  5546.             $audit = "audit " if $profile_data->{$allow}{netdomain}{audit}{$fam};
  5547.                     push @data, "${pre}${audit}${allowstr}network $fam,";
  5548.                 } else {
  5549.                     for my $type 
  5550.                         (sort keys %{$profile_data->{$allow}{netdomain}{rule}{$fam}}) {
  5551.                 $audit = "audit " if $profile_data->{$allow}{netdomain}{audit}{$fam}{$type};
  5552.                 push @data, "${pre}${audit}${allowstr}network $fam $type,";
  5553.                     }
  5554.                 }
  5555.             }
  5556.         }
  5557.         push @data, "" if %{$profile_data->{$allow}{netdomain}};
  5558.     }
  5559.     return @data;
  5560.  
  5561. }
  5562.  
  5563. sub writenetdomain ($$) {
  5564.     my ($prof_data, $depth) = @_;
  5565.     my @data;
  5566.  
  5567.     push @data, writenet_rules($prof_data, $depth, 'deny');
  5568.     push @data, writenet_rules($prof_data, $depth, 'allow');
  5569.  
  5570.     return @data;
  5571. }
  5572.  
  5573. sub writelink_rules ($$$) {
  5574.     my ($profile_data, $depth, $allow) = @_;
  5575.  
  5576.     my $allowstr = $allow eq 'deny' ? 'deny ' : '';
  5577.     my $pre = "  " x $depth;
  5578.  
  5579.     my @data;
  5580.     if (exists $profile_data->{$allow}{link}) {
  5581.         for my $path (sort keys %{$profile_data->{$allow}{link}}) {
  5582.             my $to = $profile_data->{$allow}{link}{$path}{to};
  5583.         my $subset = ($profile_data->{$allow}{link}{$path}{mode} & $AA_LINK_SUBSET) ? 'subset ' : '';
  5584.         my $audit = ($profile_data->{$allow}{link}{$path}{audit}) ? 'audit ' : '';
  5585.             # deal with whitespace in path names
  5586.             $path = quote_if_needed($path);
  5587.         $to = quote_if_needed($to);
  5588.         push @data, "${pre}${audit}${allowstr}link ${subset}${path} -> ${to},";
  5589.         }
  5590.     push @data, "";
  5591.     }
  5592.  
  5593.     return @data;
  5594. }
  5595.  
  5596. sub writelinks ($$) {
  5597.     my ($profile_data, $depth) = @_;
  5598.     my @data;
  5599.  
  5600.     push @data, writelink_rules($profile_data, $depth, 'deny');
  5601.     push @data, writelink_rules($profile_data, $depth, 'allow');
  5602.  
  5603.     return @data;
  5604. }
  5605.  
  5606. sub writepath_rules ($$$) {
  5607.     my ($profile_data, $depth, $allow) = @_;
  5608.  
  5609.     my $allowstr = $allow eq 'deny' ? 'deny ' : '';
  5610.     my $pre = "  " x $depth;
  5611.  
  5612.     my @data;
  5613.     if (exists $profile_data->{$allow}{path}) {
  5614.         for my $path (sort keys %{$profile_data->{$allow}{path}}) {
  5615.             my $mode = $profile_data->{$allow}{path}{$path}{mode};
  5616.             my $audit = $profile_data->{$allow}{path}{$path}{audit};
  5617.         my $tail = "";
  5618.         $tail = " -> " . $profile_data->{$allow}{path}{$path}{to} if ($profile_data->{$allow}{path}{$path}{to});
  5619.         my ($user, $other) = split_mode($mode);
  5620.         if ($user & ~$other) {
  5621.         $user = $user & ~$other;
  5622.         $mode = $other;
  5623.  
  5624.         if ($user & $audit) {
  5625.             my $amode = $user & $audit;
  5626.             my $modestr = mode_to_str_user($amode);
  5627.             my $str = $allowstr;
  5628.             $str .= "owner " if $modestr =~ s/owner //;
  5629.             if ($path =~ /\s/) {
  5630.             push @data, "${pre}audit ${str}\"$path\" ${modestr}${tail},";
  5631.             } else {
  5632.             push @data, "${pre}audit ${str}$path ${modestr}${tail},";
  5633.             }
  5634.             # mask off the bits we have already written
  5635.             $user &= ~$audit;
  5636.         }
  5637.         if ($user) {
  5638.             my $modestr = mode_to_str_user($user & ~$audit);
  5639.             my $str = $allowstr;
  5640.             $str .= "owner " if $modestr =~ s/owner //;
  5641.  
  5642.             # deal with whitespace in path names
  5643.             if ($path =~ /\s/) {
  5644.             push @data, "${pre}${str}\"$path\" ${modestr}${tail},";
  5645.             } else {
  5646.             push @data, "${pre}${str}$path ${modestr}${tail},";
  5647.             }
  5648.         }
  5649.         if ($mode & $audit) {
  5650.             my $amode = $mode & $audit;
  5651.             my $modestr = mode_to_str_user($amode);
  5652.             my $str = $allowstr;
  5653.             $str .= "owner " if $modestr =~ s/owner //;
  5654.             if ($path =~ /\s/) {
  5655.             push @data, "${pre}audit ${str}\"$path\" ${modestr}${tail},";
  5656.             } else {
  5657.             push @data, "${pre}audit ${str}$path ${modestr}${tail},";
  5658.             }
  5659.             # mask off the bits we have already written
  5660.             $mode &= ~$audit;
  5661.         }
  5662.         if ($mode) {
  5663.             my $modestr = mode_to_str_user($mode & ~$audit);
  5664.             my $str = $allowstr;
  5665.             $str .= "owner " if $modestr =~ s/owner //;
  5666.             # deal with whitespace in path names
  5667.             if ($path =~ /\s/) {
  5668.             push @data, "${pre}${str}\"$path\" ${modestr}${tail},";
  5669.             } else {
  5670.             push @data, "${pre}${str}$path ${modestr}${tail},";
  5671.             }
  5672.         }
  5673.         } else {
  5674.         if ($mode & $audit) {
  5675.             my $amode = $mode & $audit;
  5676.             my $modestr = mode_to_str_user($amode);
  5677.             my $str = $allowstr;
  5678.             $str .= "owner " if $modestr =~ s/owner //;
  5679.             if ($path =~ /\s/) {
  5680.             push @data, "${pre}audit ${str}\"$path\" ${modestr}${tail},";
  5681.             } else {
  5682.             push @data, "${pre}audit ${str}$path ${modestr}${tail},";
  5683.             }
  5684.             # mask off the bits we have already written
  5685.             $mode &= ~$audit;
  5686.         }
  5687.         if ($mode) {
  5688.             my $modestr = mode_to_str_user($mode & ~$audit);
  5689.             my $str = $allowstr;
  5690.             $str .= "owner " if $modestr =~ s/owner //;
  5691.             # deal with whitespace in path names
  5692.             if ($path =~ /\s/) {
  5693.             push @data, "${pre}${str}\"$path\" ${modestr}${tail},";
  5694.             } else {
  5695.             push @data, "${pre}${str}$path ${modestr}${tail},";
  5696.             }
  5697.         }
  5698.         }
  5699.         }
  5700.     push @data, "";
  5701.     }
  5702.  
  5703.     return @data;
  5704. }
  5705.  
  5706. sub writepaths ($$) {
  5707.     my ($prof_data, $depth) = @_;
  5708.  
  5709.     my @data;
  5710.     push @data, writepath_rules($prof_data, $depth, 'deny');
  5711.     push @data, writepath_rules($prof_data, $depth, 'allow');
  5712.  
  5713.     return @data;
  5714. }
  5715.  
  5716. sub write_rules ($$) {
  5717.     my ($prof_data, $depth) = @_;
  5718.  
  5719.     my @data;
  5720.     push @data, writealiases($prof_data, $depth);
  5721.     push @data, writelistvars($prof_data, $depth);
  5722.     push @data, writeincludes($prof_data, $depth);
  5723.     push @data, writerlimits($prof_data, $depth);
  5724.     push @data, writecapabilities($prof_data, $depth);
  5725.     push @data, writenetdomain($prof_data, $depth);
  5726.     push @data, writelinks($prof_data, $depth);
  5727.     push @data, writepaths($prof_data, $depth);
  5728.     push @data, writechange_profile($prof_data, $depth);
  5729.  
  5730.     return @data;
  5731. }
  5732.  
  5733. sub writepiece ($$$$$);
  5734. sub writepiece ($$$$$) {
  5735.     my ($profile_data, $depth, $name, $nhat, $write_flags) = @_;
  5736.  
  5737.     my $pre = '  ' x $depth;
  5738.     my @data;
  5739.     my $wname;
  5740.     my $inhat = 0;
  5741.     if ($name eq $nhat) {
  5742.     $wname = $name;
  5743.     } else {
  5744.     $wname = "$name//$nhat";
  5745.     $name = $nhat;
  5746.     $inhat = 1;
  5747.     }
  5748.     push @data, writeheader($profile_data->{$name}, $depth, $wname, 0, $write_flags);
  5749.     push @data, write_rules($profile_data->{$name}, $depth + 1);
  5750.  
  5751.     my $pre2 = '  ' x ($depth + 1);
  5752.     # write external hat declarations
  5753.     for my $hat (grep { $_ ne $name } sort keys %{$profile_data}) {
  5754.     if ($profile_data->{$hat}{declared}) {
  5755.         push @data, "${pre2}^$hat,";
  5756.     }
  5757.     }
  5758.  
  5759.     if (!$inhat) {
  5760.     # write embedded hats
  5761.     for my $hat (grep { $_ ne $name } sort keys %{$profile_data}) {
  5762.         if ((not $profile_data->{$hat}{external}) and
  5763.         (not $profile_data->{$hat}{declared})) {
  5764.         push @data, "";
  5765.         if ($profile_data->{$hat}{profile}) {
  5766.             push @data, map { "$_" } writeheader($profile_data->{$hat},
  5767.                              $depth + 1, $hat,
  5768.                              1, $write_flags);
  5769.         } else {
  5770.             push @data, map { "$_" } writeheader($profile_data->{$hat},
  5771.                              $depth + 1, "^$hat",
  5772.                              1, $write_flags);
  5773.         }
  5774.         push @data, map { "$_" } write_rules($profile_data->{$hat},
  5775.                              $depth + 2);
  5776.         push @data, "${pre2}}";
  5777.         }
  5778.     }
  5779.     push @data, "${pre}}";
  5780.  
  5781.     #write external hats
  5782.     for my $hat (grep { $_ ne $name } sort keys %{$profile_data}) {
  5783.         if (($name eq $nhat) and $profile_data->{$hat}{external}) {
  5784.         push @data, "";
  5785.         push @data, map { "  $_" } writepiece($profile_data, $depth - 1,
  5786.                               $name, $hat, $write_flags);
  5787.         push @data, "  }";
  5788.         }
  5789.     }
  5790.     }
  5791.     return @data;
  5792. }
  5793.  
  5794. sub serialize_profile {
  5795.     my ($profile_data, $name, $options) = @_;
  5796.  
  5797.     my $string = "";
  5798.     my $include_metadata = 0;  # By default don't write out metadata
  5799.     my $include_flags = 1;
  5800.     if ( $options and ref($options) eq "HASH" ) {
  5801.        $include_metadata = 1 if ( defined $options->{METADATA} );
  5802.        $include_flags    = 0 if ( defined $options->{NO_FLAGS} );
  5803.     }
  5804.  
  5805.     if ($include_metadata) {
  5806.         # keep track of when the file was last updated
  5807.         $string .= "# Last Modified: " . localtime(time) . "\n";
  5808.  
  5809.         # print out repository metadata
  5810.         if ($profile_data->{$name}{repo}       &&
  5811.             $profile_data->{$name}{repo}{url}  &&
  5812.             $profile_data->{$name}{repo}{user} &&
  5813.             $profile_data->{$name}{repo}{id}) {
  5814.             my $repo = $profile_data->{$name}{repo};
  5815.             $string .= "# REPOSITORY: $repo->{url} $repo->{user} $repo->{id}\n";
  5816.         } elsif ($profile_data->{$name}{repo}{neversubmit}) {
  5817.             $string .= "# REPOSITORY: NEVERSUBMIT\n";
  5818.         }
  5819.     }
  5820.  
  5821.     # print out initial comment
  5822.     if ($profile_data->{$name}{initial_comment}) {
  5823.         my $comment = $profile_data->{$name}{initial_comment};
  5824.         $comment =~ s/\\n/\n/g;
  5825.         $string .= "$comment\n";
  5826.     }
  5827.  
  5828.     #bleah this is stupid the data structure needs to be reworked
  5829.     my $filename = getprofilefilename($name);
  5830.     my @data;
  5831.     if ($filelist{$filename}) {
  5832.     push @data, writealiases($filelist{$filename}, 0);
  5833.     push @data, writelistvars($filelist{$filename}, 0);
  5834.     push @data, writeincludes($filelist{$filename}, 0);
  5835.     }
  5836.  
  5837.  
  5838. # XXX - FIXME
  5839. #
  5840. #  # dump variables defined in this file
  5841. #  if ($variables{$filename}) {
  5842. #    for my $var (sort keys %{$variables{$filename}}) {
  5843. #      if ($var =~ m/^@/) {
  5844. #        my @values = sort @{$variables{$filename}{$var}};
  5845. #        @values = map { escape($_) } @values;
  5846. #        my $values = join (" ", @values);
  5847. #        print SDPROF "$var = ";
  5848. #        print SDPROF $values;
  5849. #      } elsif ($var =~ m/^\$/) {
  5850. #        print SDPROF "$var = ";
  5851. #        print SDPROF ${$variables{$filename}{$var}};
  5852. #      } elsif ($var =~ m/^\#/) {
  5853. #        my $inc = $var;
  5854. #        $inc =~ s/^\#//;
  5855. #        print SDPROF "#include <$inc>";
  5856. #      }
  5857. #      print SDPROF "\n";
  5858. #    }
  5859. #  }
  5860.  
  5861.     push @data, writepiece($profile_data, 0, $name, $name, $include_flags);
  5862.     $string .= join("\n", @data);
  5863.  
  5864.     return "$string\n";
  5865. }
  5866.  
  5867. sub writeprofile_ui_feedback ($) {
  5868.     my $profile = shift;
  5869.     UI_Info(sprintf(gettext('Writing updated profile for %s.'), $profile));
  5870.     writeprofile($profile);
  5871. }
  5872.  
  5873. sub writeprofile ($) {
  5874.     my ($profile) = shift;
  5875.  
  5876.     my $filename = $sd{$profile}{$profile}{filename} || getprofilefilename($profile);
  5877.  
  5878.     open(SDPROF, ">$filename") or
  5879.       fatal_error "Can't write new AppArmor profile $filename: $!";
  5880.     my $serialize_opts = { };
  5881.     $serialize_opts->{METADATA} = 1;
  5882.  
  5883.     #make sure to write out all the profiles in the file
  5884.     my $profile_string = serialize_profile($sd{$profile}, $profile, $serialize_opts);
  5885.     print SDPROF $profile_string;
  5886.     close(SDPROF);
  5887.  
  5888.     # mark the profile as up-to-date
  5889.     delete $changed{$profile};
  5890.     $original_sd{$profile} = dclone($sd{$profile});
  5891. }
  5892.  
  5893. sub getprofileflags {
  5894.     my $filename = shift;
  5895.  
  5896.     my $flags = "enforce";
  5897.  
  5898.     if (open(PROFILE, "$filename")) {
  5899.         while (<PROFILE>) {
  5900.             if (m/^\s*\/\S+\s+flags=\((.+)\)\s+{\s*$/) {
  5901.                 $flags = $1;
  5902.                 close(PROFILE);
  5903.                 return $flags;
  5904.             }
  5905.         }
  5906.         close(PROFILE);
  5907.     }
  5908.  
  5909.     return $flags;
  5910. }
  5911.  
  5912.  
  5913. sub matchliteral {
  5914.     my ($sd_regexp, $literal) = @_;
  5915.  
  5916.     my $p_regexp = convert_regexp($sd_regexp);
  5917.  
  5918.     # check the log entry against our converted regexp...
  5919.     my $matches = eval { $literal =~ /^$p_regexp$/; };
  5920.  
  5921.     # doesn't match if we've got a broken regexp
  5922.     return undef if $@;
  5923.  
  5924.     return $matches;
  5925. }
  5926.  
  5927. # test if profile has exec rule for $exec_target
  5928. sub profile_known_exec (\%$$) {
  5929.     my ($profile, $type, $exec_target) = @_;
  5930.     if ( $type eq "exec" ) {
  5931.         my ($cm, $am, @m);
  5932.  
  5933.         # test denies first
  5934.         ($cm, $am, @m) = rematchfrag($profile, 'deny', $exec_target);
  5935.     if ($cm & $AA_MAY_EXEC) {
  5936.         return -1;
  5937.     }
  5938.         ($cm, $am, @m) = match_prof_incs_to_path($profile, 'deny', $exec_target);
  5939.     if ($cm & $AA_MAY_EXEC) {
  5940.         return -1;
  5941.     }
  5942.  
  5943.     # now test the generally longer allow lists
  5944.         ($cm, $am, @m) = rematchfrag($profile, 'allow', $exec_target);
  5945.     if ($cm & $AA_MAY_EXEC) {
  5946.         return 1;
  5947.     }
  5948.  
  5949.         ($cm, $am, @m) = match_prof_incs_to_path($profile, 'allow', $exec_target);
  5950.     if ($cm & $AA_MAY_EXEC) {
  5951.         return 1;
  5952.     }
  5953.     }
  5954.     return 0;
  5955. }
  5956.  
  5957. sub profile_known_capability (\%$) {
  5958.     my ($profile, $capname) = @_;
  5959.  
  5960.     return -1 if $profile->{deny}{capability}{$capname}{set};
  5961.     return 1 if $profile->{allow}{capability}{$capname}{set};
  5962.     for my $incname ( keys %{$profile->{include}} ) {
  5963.     return -1 if $include{$incname}{$incname}{deny}{capability}{$capname}{set};
  5964.     return 1 if $include{$incname}{$incname}{allow}{capability}{$capname}{set};
  5965.     }
  5966.     return 0;
  5967. }
  5968.  
  5969. sub profile_known_network (\%$$) {
  5970.     my ($profile, $family, $sock_type) = @_;
  5971.  
  5972.     return -1 if netrules_access_check( $profile->{deny}{netdomain},
  5973.                                        $family, $sock_type);
  5974.     return 1 if netrules_access_check( $profile->{allow}{netdomain},
  5975.                                        $family, $sock_type);
  5976.  
  5977.     for my $incname ( keys %{$profile->{include}} ) {
  5978.         return -1 if netrules_access_check($include{$incname}{$incname}{deny}{netdomain},
  5979.                                         $family, $sock_type);
  5980.         return 1 if netrules_access_check($include{$incname}{$incname}{allow}{netdomain},
  5981.                       $family, $sock_type);
  5982.     }
  5983.  
  5984.     return 0;
  5985. }
  5986.  
  5987. sub netrules_access_check ($$$) {
  5988.     my ($netrules, $family, $sock_type) = @_;
  5989.     return 0 if ( not defined $netrules );
  5990.     my %netrules        = %$netrules;
  5991.     my $all_net         = defined $netrules{rule}{all};
  5992.     my $all_net_family  = defined $netrules{rule}{$family} && $netrules{rule}{$family} == 1;
  5993.     my $net_family_sock = defined $netrules{rule}{$family} &&
  5994.                           ref($netrules{rule}{$family}) eq "HASH" &&
  5995.                           defined $netrules{rule}{$family}{$sock_type};
  5996.  
  5997.     if ( $all_net || $all_net_family || $net_family_sock ) {
  5998.         return 1;
  5999.     } else {
  6000.       return 0;
  6001.     }
  6002. }
  6003.  
  6004. sub reload_base($) {
  6005.     my $bin = shift;
  6006.  
  6007.     # don't try to reload profile if AppArmor is not running
  6008.     return unless check_for_subdomain();
  6009.  
  6010.     my $filename = getprofilefilename($bin);
  6011.  
  6012.     system("/bin/cat '$filename' | $parser -I$profiledir -r >/dev/null 2>&1");
  6013. }
  6014.  
  6015. sub reload ($) {
  6016.     my $bin = shift;
  6017.  
  6018.     # don't reload the profile if the corresponding executable doesn't exist
  6019.     my $fqdbin = findexecutable($bin) or return;
  6020.  
  6021.     return reload_base($fqdbin);
  6022. }
  6023.  
  6024. sub read_include_from_file {
  6025.     my $which = shift;
  6026.  
  6027.     my $data;
  6028.     if (open(INCLUDE, "$profiledir/$which")) {
  6029.         local $/;
  6030.         $data = <INCLUDE>;
  6031.         close(INCLUDE);
  6032.     }
  6033.  
  6034.     return $data;
  6035. }
  6036.  
  6037. sub get_include_data {
  6038.     my $which = shift;
  6039.  
  6040.     my $data = read_include_from_file($which);
  6041.     unless($data) {
  6042.         fatal_error "Can't find include file $which: $!";
  6043.     }
  6044.     return $data;
  6045. }
  6046.  
  6047. sub loadinclude {
  6048.     my $which = shift;
  6049.  
  6050.     # don't bother loading it again if we already have
  6051.     return 0 if $include{$which}{$which};
  6052.  
  6053.     my @loadincludes = ($which);
  6054.     while (my $incfile = shift @loadincludes) {
  6055.  
  6056.         my $data = get_include_data($incfile);
  6057.     my $incdata = parse_profile_data($data, $incfile, 1);
  6058.     if ($incdata) {
  6059.                     attach_profile_data(\%include, $incdata);
  6060.     }
  6061.     }
  6062.     return 0;
  6063. }
  6064.  
  6065. sub rematchfrag ($$$) {
  6066.     my ($frag, $allow, $path) = @_;
  6067.  
  6068.     my $combinedmode = 0;
  6069.     my $combinedaudit = 0;
  6070.     my @matches;
  6071.  
  6072.     for my $entry (keys %{ $frag->{$allow}{path} }) {
  6073.  
  6074.         my $regexp = convert_regexp($entry);
  6075.  
  6076.         # check the log entry against our converted regexp...
  6077.         if ($path =~ /^$regexp$/) {
  6078.  
  6079.             # regexp matches, add it's mode to the list to check against
  6080.             $combinedmode |= $frag->{$allow}{path}{$entry}{mode};
  6081.             $combinedaudit |= $frag->{$allow}{path}{$entry}{audit};
  6082.             push @matches, $entry;
  6083.         }
  6084.     }
  6085.  
  6086.     return wantarray ? ($combinedmode, $combinedaudit, @matches) : $combinedmode;
  6087. }
  6088.  
  6089. sub match_include_to_path ($$$) {
  6090.     my ($incname, $allow, $path) = @_;
  6091.  
  6092.     my $combinedmode = 0;
  6093.     my $combinedaudit = 0;
  6094.     my @matches;
  6095.  
  6096.     my @includelist = ( $incname );
  6097.     while (my $incfile = shift @includelist) {
  6098.         my $ret = eval { loadinclude($incfile); };
  6099.         if ($@) { fatal_error $@; }
  6100.         my ($cm, $am, @m) = rematchfrag($include{$incfile}{$incfile}, $allow, $path);
  6101.         if ($cm) {
  6102.             $combinedmode |= $cm;
  6103.         $combinedaudit |= $am;
  6104.             push @matches, @m;
  6105.         }
  6106.  
  6107.         # check if a literal version is in the current include fragment
  6108.         if ($include{$incfile}{$incfile}{$allow}{path}{$path}) {
  6109.             $combinedmode |= $include{$incfile}{$incfile}{$allow}{path}{$path}{mode};
  6110.             $combinedaudit |= $include{$incfile}{$incfile}{$allow}{path}{$path}{audit};
  6111.         }
  6112.  
  6113.         # if this fragment includes others, check them too
  6114.         if (keys %{ $include{$incfile}{$incfile}{include} }) {
  6115.             push @includelist, keys %{ $include{$incfile}{$incfile}{include} };
  6116.         }
  6117.     }
  6118.  
  6119.     return wantarray ? ($combinedmode, $combinedaudit, @matches) : $combinedmode;
  6120. }
  6121.  
  6122. sub match_prof_incs_to_path ($$$) {
  6123.     my ($frag, $allow, $path) = @_;
  6124.  
  6125.     my $combinedmode = 0;
  6126.     my $combinedaudit = 0;
  6127.     my @matches;
  6128.  
  6129.     # scan the include fragments for this profile looking for matches
  6130.     my @includelist = keys %{ $frag->{include} };
  6131.     while (my $include = shift @includelist) {
  6132.     my ($cm, $am, @m) = match_include_to_path($include, $allow, $path);
  6133.         if ($cm) {
  6134.             $combinedmode |= $cm;
  6135.             $combinedaudit |= $am;
  6136.             push @matches, @m;
  6137.         }
  6138.     }
  6139.  
  6140.     return wantarray ? ($combinedmode, $combinedaudit, @matches) : $combinedmode;
  6141. }
  6142.  
  6143. #find includes that match the path to suggest
  6144. sub suggest_incs_for_path {
  6145.     my ($incname, $path, $allow) = @_;
  6146.  
  6147.  
  6148.     my $combinedmode = 0;
  6149.     my $combinedaudit = 0;
  6150.     my @matches;
  6151.  
  6152.     # scan the include fragments looking for matches
  6153.     my @includelist = ($incname);
  6154.     while (my $include = shift @includelist) {
  6155.         my ($cm, $am, @m) = rematchfrag($include{$include}{$include}, 'allow', $path);
  6156.         if ($cm) {
  6157.             $combinedmode |= $cm;
  6158.             $combinedaudit |= $am;
  6159.             push @matches, @m;
  6160.         }
  6161.  
  6162.         # check if a literal version is in the current include fragment
  6163.         if ($include{$include}{$include}{allow}{path}{$path}) {
  6164.             $combinedmode |= $include{$include}{$include}{allow}{path}{$path}{mode};
  6165.             $combinedaudit |= $include{$include}{$include}{allow}{path}{$path}{audit};
  6166.         }
  6167.  
  6168.         # if this fragment includes others, check them too
  6169.         if (keys %{ $include{$include}{$include}{include} }) {
  6170.             push @includelist, keys %{ $include{$include}{$include}{include} };
  6171.         }
  6172.     }
  6173.  
  6174.     if ($combinedmode) {
  6175.         return wantarray ? ($combinedmode, $combinedaudit, @matches) : $combinedmode;
  6176.     } else {
  6177.         return;
  6178.     }
  6179. }
  6180.  
  6181. sub check_qualifiers {
  6182.     my $program = shift;
  6183.  
  6184.     if ($cfg->{qualifiers}{$program}) {
  6185.         unless($cfg->{qualifiers}{$program} =~ /p/) {
  6186.             fatal_error(sprintf(gettext("\%s is currently marked as a program that should not have it's own profile.  Usually, programs are marked this way if creating a profile for them is likely to break the rest of the system.  If you know what you're doing and are certain you want to create a profile for this program, edit the corresponding entry in the [qualifiers] section in /etc/apparmor/logprof.conf."), $program));
  6187.         }
  6188.     }
  6189. }
  6190.  
  6191. sub loadincludes {
  6192.     if (opendir(SDDIR, $profiledir)) {
  6193.         my @incdirs = grep { (!/^\./) && (-d "$profiledir/$_") } readdir(SDDIR);
  6194.         close(SDDIR);
  6195.  
  6196.         while (my $id = shift @incdirs) {
  6197.             if (opendir(SDDIR, "$profiledir/$id")) {
  6198.                 for my $path (readdir(SDDIR)) {
  6199.                     chomp($path);
  6200.                     next if isSkippableFile($path);
  6201.                     if (-f "$profiledir/$id/$path") {
  6202.                         my $file = "$id/$path";
  6203.                         $file =~ s/$profiledir\///;
  6204.                         my $ret = eval { loadinclude($file); };
  6205.                         if ($@) { fatal_error $@; }
  6206.                     } elsif (-d "$id/$path") {
  6207.                         push @incdirs, "$id/$path";
  6208.                     }
  6209.                 }
  6210.                 closedir(SDDIR);
  6211.             }
  6212.         }
  6213.     }
  6214. }
  6215.  
  6216. sub globcommon ($) {
  6217.     my $path = shift;
  6218.  
  6219.     my @globs;
  6220.  
  6221.     # glob library versions in both foo-5.6.so and baz.so.9.2 form
  6222.     if ($path =~ m/[\d\.]+\.so$/ || $path =~ m/\.so\.[\d\.]+$/) {
  6223.         my $libpath = $path;
  6224.         $libpath =~ s/[\d\.]+\.so$/*.so/;
  6225.         $libpath =~ s/\.so\.[\d\.]+$/.so.*/;
  6226.         push @globs, $libpath if $libpath ne $path;
  6227.     }
  6228.  
  6229.     for my $glob (keys %{$cfg->{globs}}) {
  6230.         if ($path =~ /$glob/) {
  6231.             my $globbedpath = $path;
  6232.             $globbedpath =~ s/$glob/$cfg->{globs}{$glob}/g;
  6233.             push @globs, $globbedpath if $globbedpath ne $path;
  6234.         }
  6235.     }
  6236.  
  6237.     if (wantarray) {
  6238.         return sort { length($b) <=> length($a) } uniq(@globs);
  6239.     } else {
  6240.         my @list = sort { length($b) <=> length($a) } uniq(@globs);
  6241.         return $list[$#list];
  6242.     }
  6243. }
  6244.  
  6245. # this is an ugly, nasty function that attempts to see if one regexp
  6246. # is a subset of another regexp
  6247. sub matchregexp ($$) {
  6248.     my ($new, $old) = @_;
  6249.  
  6250.     # bail out if old pattern has {foo,bar,baz} stuff in it
  6251.     return undef if $old =~ /\{.*(\,.*)*\}/;
  6252.  
  6253.     # are there any regexps at all in the old pattern?
  6254.     if ($old =~ /\[.+\]/ or $old =~ /\*/ or $old =~ /\?/) {
  6255.  
  6256.         # convert {foo,baz} to (foo|baz)
  6257.         $new =~ y/\{\}\,/\(\)\|/ if $new =~ /\{.*\,.*\}/;
  6258.  
  6259.         # \001 == SD_GLOB_RECURSIVE
  6260.         # \002 == SD_GLOB_SIBLING
  6261.  
  6262.         $new =~ s/\*\*/\001/g;
  6263.         $new =~ s/\*/\002/g;
  6264.  
  6265.         $old =~ s/\*\*/\001/g;
  6266.         $old =~ s/\*/\002/g;
  6267.  
  6268.         # strip common prefix
  6269.         my $prefix = commonprefix($new, $old);
  6270.         if ($prefix) {
  6271.  
  6272.             # make sure we don't accidentally gobble up a trailing * or **
  6273.             $prefix =~ s/(\001|\002)$//;
  6274.             $new    =~ s/^$prefix//;
  6275.             $old    =~ s/^$prefix//;
  6276.         }
  6277.  
  6278.         # strip common suffix
  6279.         my $suffix = commonsuffix($new, $old);
  6280.         if ($suffix) {
  6281.  
  6282.             # make sure we don't accidentally gobble up a leading * or **
  6283.             $suffix =~ s/^(\001|\002)//;
  6284.             $new    =~ s/$suffix$//;
  6285.             $old    =~ s/$suffix$//;
  6286.         }
  6287.  
  6288.         # if we boiled the differences down to a ** in the new entry, it matches
  6289.         # whatever's in the old entry
  6290.         return 1 if $new eq "\001";
  6291.  
  6292.         # if we've paired things down to a * in new, old matches if there are no
  6293.         # slashes left in the path
  6294.         return 1 if ($new eq "\002" && $old =~ /^[^\/]+$/);
  6295.  
  6296.         # we'll bail out if we have more globs in the old version
  6297.         return undef if $old =~ /\001|\002/;
  6298.  
  6299.         # see if we can match * globs in new against literal elements in old
  6300.         $new =~ s/\002/[^\/]*/g;
  6301.  
  6302.         return 1 if $old =~ /^$new$/;
  6303.  
  6304.     } else {
  6305.  
  6306.         my $new_regexp = convert_regexp($new);
  6307.  
  6308.         # check the log entry against our converted regexp...
  6309.         return 1 if $old =~ /^$new_regexp$/;
  6310.  
  6311.     }
  6312.  
  6313.     return undef;
  6314. }
  6315.  
  6316. sub combine_name($$) { return ($_[0] eq $_[1]) ? $_[0] : "$_[0]^$_[1]"; }
  6317. sub split_name ($) { my ($p, $h) = split(/\^/, $_[0]); $h ||= $p; ($p, $h); }
  6318.  
  6319. ##########################
  6320. #
  6321. # prompt_user($headers, $functions, $default, $options, $selected);
  6322. #
  6323. # $headers:
  6324. #   a required arrayref made up of "key, value" pairs in the order you'd
  6325. #   like them displayed to user
  6326. #
  6327. # $functions:
  6328. #   a required arrayref of the different options to display at the bottom
  6329. #   of the prompt like "(A)llow", "(D)eny", and "Ba(c)on".  the character
  6330. #   contained by ( and ) will be used as the key to select the specified
  6331. #   option.
  6332. #
  6333. # $default:
  6334. #   a required character which is the default "key" to enter when they
  6335. #   just hit enter
  6336. #
  6337. # $options:
  6338. #   an optional arrayref of the choices like the glob suggestions to be
  6339. #   presented to the user
  6340. #
  6341. # $selected:
  6342. #   specifies which option is currently selected
  6343. #
  6344. # when prompt_user() is called without an $options list, it returns a
  6345. # single value which is the key for the specified "function".
  6346. #
  6347. # when prompt_user() is called with an $options list, it returns an array
  6348. # of two elements, the key for the specified function as well as which
  6349. # option was currently selected
  6350. #######################################################################
  6351.  
  6352. sub Text_PromptUser ($) {
  6353.     my $question = shift;
  6354.  
  6355.     my $title     = $question->{title};
  6356.     my $explanation = $question->{explanation};
  6357.  
  6358.     my @headers   = (@{ $question->{headers} });
  6359.     my @functions = (@{ $question->{functions} });
  6360.  
  6361.     my $default  = $question->{default};
  6362.     my $options  = $question->{options};
  6363.     my $selected = $question->{selected} || 0;
  6364.  
  6365.     my $helptext = $question->{helptext};
  6366.  
  6367.     push @functions, "CMD_HELP" if $helptext;
  6368.  
  6369.     my %keys;
  6370.     my @menu_items;
  6371.     for my $cmd (@functions) {
  6372.  
  6373.         # make sure we know about this particular command
  6374.         my $cmdmsg = "PromptUser: " . gettext("Unknown command") . " $cmd";
  6375.         fatal_error $cmdmsg unless $CMDS{$cmd};
  6376.  
  6377.         # grab the localized text to use for the menu for this command
  6378.         my $menutext = gettext($CMDS{$cmd});
  6379.  
  6380.         # figure out what the hotkey for this menu item is
  6381.         my $menumsg = "PromptUser: " .
  6382.                       gettext("Invalid hotkey in") .
  6383.                       " '$menutext'";
  6384.         $menutext =~ /\((\S)\)/ or fatal_error $menumsg;
  6385.  
  6386.         # we want case insensitive comparisons so we'll force things to
  6387.         # lowercase
  6388.         my $key = lc($1);
  6389.  
  6390.         # check if we're already using this hotkey for this prompt
  6391.         my $hotkeymsg = "PromptUser: " .
  6392.                         gettext("Duplicate hotkey for") .
  6393.                         " $cmd: $menutext";
  6394.         fatal_error $hotkeymsg if $keys{$key};
  6395.  
  6396.         # keep track of which command they're picking if they hit this hotkey
  6397.         $keys{$key} = $cmd;
  6398.  
  6399.         if ($default && $default eq $cmd) {
  6400.             $menutext = "[$menutext]";
  6401.         }
  6402.  
  6403.         push @menu_items, $menutext;
  6404.     }
  6405.  
  6406.     # figure out the key for the default option
  6407.     my $default_key;
  6408.     if ($default && $CMDS{$default}) {
  6409.         my $defaulttext = gettext($CMDS{$default});
  6410.  
  6411.         # figure out what the hotkey for this menu item is
  6412.         my $defmsg = "PromptUser: " .
  6413.                       gettext("Invalid hotkey in default item") .
  6414.                       " '$defaulttext'";
  6415.         $defaulttext =~ /\((\S)\)/ or fatal_error $defmsg;
  6416.  
  6417.         # we want case insensitive comparisons so we'll force things to
  6418.         # lowercase
  6419.         $default_key = lc($1);
  6420.  
  6421.         my $defkeymsg = "PromptUser: " .
  6422.                         gettext("Invalid default") .
  6423.                         " $default";
  6424.         fatal_error $defkeymsg unless $keys{$default_key};
  6425.     }
  6426.  
  6427.     my $widest = 0;
  6428.     my @poo    = @headers;
  6429.     while (my $header = shift @poo) {
  6430.         my $value = shift @poo;
  6431.         $widest = length($header) if length($header) > $widest;
  6432.     }
  6433.     $widest++;
  6434.  
  6435.     my $format = '%-' . $widest . "s \%s\n";
  6436.  
  6437.     my $function_regexp = '^(';
  6438.     $function_regexp .= join("|", keys %keys);
  6439.     $function_regexp .= '|\d' if $options;
  6440.     $function_regexp .= ')$';
  6441.  
  6442.     my $ans = "XXXINVALIDXXX";
  6443.     while ($ans !~ /$function_regexp/i) {
  6444.         # build up the prompt...
  6445.         my $prompt = "\n";
  6446.  
  6447.         $prompt .= "= $title =\n\n" if $title;
  6448.  
  6449.         if (@headers) {
  6450.             my @poo = @headers;
  6451.             while (my $header = shift @poo) {
  6452.                 my $value = shift @poo;
  6453.                 $prompt .= sprintf($format, "$header:", $value);
  6454.             }
  6455.             $prompt .= "\n";
  6456.         }
  6457.  
  6458.         if ($explanation) {
  6459.             $prompt .= "$explanation\n\n";
  6460.         }
  6461.  
  6462.         if ($options) {
  6463.             for (my $i = 0; $options->[$i]; $i++) {
  6464.                 my $f = ($selected == $i) ? ' [%d - %s]' : '  %d - %s ';
  6465.                 $prompt .= sprintf("$f\n", $i + 1, $options->[$i]);
  6466.             }
  6467.             $prompt .= "\n";
  6468.         }
  6469.         $prompt .= join(" / ", @menu_items);
  6470.         print "$prompt\n";
  6471.  
  6472.         # get their input...
  6473.         $ans = lc(getkey());
  6474.  
  6475.         if ($ans) {
  6476.             # handle escape sequences so you can up/down in the list
  6477.             if ($ans eq "up") {
  6478.  
  6479.                 if ($options && ($selected > 0)) {
  6480.                     $selected--;
  6481.                 }
  6482.                 $ans = "XXXINVALIDXXX";
  6483.  
  6484.             } elsif ($ans eq "down") {
  6485.  
  6486.                 if ($options && ($selected < (scalar(@$options) - 1))) {
  6487.                     $selected++;
  6488.                 }
  6489.                 $ans = "XXXINVALIDXXX";
  6490.  
  6491.             } elsif ($keys{$ans} && $keys{$ans} eq "CMD_HELP") {
  6492.  
  6493.                 print "\n$helptext\n";
  6494.                 $ans = "XXXINVALIDXXX";
  6495.  
  6496.             } elsif (ord($ans) == 10) {
  6497.  
  6498.                 # pick the default if they hit return...
  6499.                 $ans = $default_key;
  6500.  
  6501.             } elsif ($options && ($ans =~ /^\d$/)) {
  6502.  
  6503.                 # handle option poo
  6504.                 if ($ans > 0 && $ans <= scalar(@$options)) {
  6505.                     $selected = $ans - 1;
  6506.                 }
  6507.                 $ans = "XXXINVALIDXXX";
  6508.             }
  6509.         }
  6510.  
  6511.         if ($keys{$ans} && $keys{$ans} eq "CMD_HELP") {
  6512.             print "\n$helptext\n";
  6513.             $ans = "again";
  6514.         }
  6515.     }
  6516.  
  6517.     # pull our command back from our hotkey map
  6518.     $ans = $keys{$ans} if $keys{$ans};
  6519.     return ($ans, $selected);
  6520.  
  6521. }
  6522.  
  6523. # Parse event record into key-value pairs
  6524. sub parse_event($) {
  6525.     my %ev = ();
  6526.     my $msg = shift;
  6527.     chomp($msg);
  6528.     my $event = LibAppArmor::parse_record($msg);
  6529.     my ($rmask, $dmask);
  6530.  
  6531.     $DEBUGGING && debug("parse_event: $msg");
  6532.  
  6533.     $ev{'resource'}   = LibAppArmor::aa_log_record::swig_info_get($event);
  6534.     $ev{'active_hat'} = LibAppArmor::aa_log_record::swig_active_hat_get($event);
  6535.     $ev{'sdmode'}     = LibAppArmor::aa_log_record::swig_event_get($event);
  6536.     $ev{'time'}       = LibAppArmor::aa_log_record::swig_epoch_get($event);
  6537.     $ev{'operation'}  = LibAppArmor::aa_log_record::swig_operation_get($event);
  6538.     $ev{'profile'}    = LibAppArmor::aa_log_record::swig_profile_get($event);
  6539.     $ev{'name'}       = LibAppArmor::aa_log_record::swig_name_get($event);
  6540.     $ev{'name2'}      = LibAppArmor::aa_log_record::swig_name2_get($event);
  6541.     $ev{'attr'}       = LibAppArmor::aa_log_record::swig_attribute_get($event);
  6542.     $ev{'parent'}     = LibAppArmor::aa_log_record::swig_parent_get($event);
  6543.     $ev{'pid'}        = LibAppArmor::aa_log_record::swig_pid_get($event);
  6544.     $ev{'task'}        = LibAppArmor::aa_log_record::swig_task_get($event);
  6545.     $ev{'info'}        = LibAppArmor::aa_log_record::swig_info_get($event);
  6546.     $dmask = LibAppArmor::aa_log_record::swig_denied_mask_get($event);
  6547.     $rmask = LibAppArmor::aa_log_record::swig_requested_mask_get($event);
  6548.     $ev{'magic_token'}  =
  6549.        LibAppArmor::aa_log_record::swig_magic_token_get($event);
  6550.  
  6551.     # NetDomain
  6552.     if ( $ev{'operation'} && $ev{'operation'} =~ /socket/ ) {
  6553.         $ev{'family'}    =
  6554.             LibAppArmor::aa_log_record::swig_net_family_get($event);
  6555.         $ev{'protocol'}  =
  6556.             LibAppArmor::aa_log_record::swig_net_protocol_get($event);
  6557.         $ev{'sock_type'} =
  6558.             LibAppArmor::aa_log_record::swig_net_sock_type_get($event);
  6559.     }
  6560.  
  6561.     LibAppArmor::free_record($event);
  6562.  
  6563.     if ($rmask && !validate_log_mode(hide_log_mode($rmask))) {
  6564.         fatal_error(sprintf(gettext('Log contains unknown mode %s.'),
  6565.                             $rmask));
  6566.     }
  6567.  
  6568.     if ($dmask && !validate_log_mode(hide_log_mode($dmask))) {
  6569.         fatal_error(sprintf(gettext('Log contains unknown mode %s.'),
  6570.                     $dmask));
  6571.     }
  6572. #print "str_to_mode deny $dmask = " . str_to_mode($dmask) . "\n" if ($dmask);
  6573. #print "str_to_mode req $rmask = "  . str_to_mode($rmask) . "\n" if ($rmask);
  6574.  
  6575.     my ($mask, $name);
  6576.     ($mask, $name) = log_str_to_mode($ev{profile}, $dmask, $ev{name2});
  6577.     $ev{'denied_mask'} = $mask;
  6578.     $ev{name2} = $name;
  6579.  
  6580.     ($mask, $name) = log_str_to_mode($ev{profile}, $rmask, $ev{name2});
  6581.     $ev{'request_mask'} = $mask;
  6582.     $ev{name2} = $name;
  6583.  
  6584.     if ( ! $ev{'time'} ) { $ev{'time'} = time; }
  6585.  
  6586.     # remove null responses
  6587.     for (keys(%ev)) {
  6588.         if ( ! $ev{$_} || $ev{$_} !~ /[\/\w]+/)  { delete($ev{$_}); }
  6589.     }
  6590.  
  6591.     if ( $ev{'sdmode'} ) {
  6592.         #0 = invalid, 1 = error, 2 = AUDIT, 3 = ALLOW/PERMIT,
  6593.         #4 = DENIED/REJECTED, 5 = HINT, 6 = STATUS/config change
  6594.         if    ( $ev{'sdmode'} == 0 ) { $ev{'sdmode'} = "UNKNOWN"; }
  6595.         elsif ( $ev{'sdmode'} == 1 ) { $ev{'sdmode'} = "ERROR"; }
  6596.         elsif ( $ev{'sdmode'} == 2 ) { $ev{'sdmode'} = "AUDITING"; }
  6597.         elsif ( $ev{'sdmode'} == 3 ) { $ev{'sdmode'} = "PERMITTING"; }
  6598.         elsif ( $ev{'sdmode'} == 4 ) { $ev{'sdmode'} = "REJECTING"; }
  6599.         elsif ( $ev{'sdmode'} == 5 ) { $ev{'sdmode'} = "HINT"; }
  6600.         elsif ( $ev{'sdmode'} == 6 ) { $ev{'sdmode'} = "STATUS"; }
  6601.         else  { delete($ev{'sdmode'}); }
  6602.     }
  6603.     if ( $ev{sdmode} ) {
  6604.        $DEBUGGING && debug( Data::Dumper->Dump([%ev], [qw(*event)]));
  6605.        return \%ev;
  6606.     } else {
  6607.        return( undef );
  6608.     }
  6609. }
  6610.  
  6611. ###############################################################################
  6612. # required initialization
  6613.  
  6614. $cfg = read_config("logprof.conf");
  6615. if ((not defined $cfg->{settings}{default_owner_prompt})) {
  6616.     $cfg->{settings}{default_owner_prompt} = 0;
  6617. }
  6618.  
  6619. $profiledir = find_first_dir($cfg->{settings}{profiledir}) || "/etc/apparmor.d";
  6620. unless (-d $profiledir) { fatal_error "Can't find AppArmor profiles."; }
  6621.  
  6622. $extraprofiledir = find_first_dir($cfg->{settings}{inactive_profiledir}) ||
  6623. "/etc/apparmor/profiles/extras/";
  6624.  
  6625. $parser = find_first_file($cfg->{settings}{parser}) || "/sbin/apparmor_parser";
  6626. unless (-x $parser) { fatal_error "Can't find apparmor_parser."; }
  6627.  
  6628. $filename = find_first_file($cfg->{settings}{logfiles}) || "/var/log/messages";
  6629. unless (-f $filename) { fatal_error "Can't find system log."; }
  6630.  
  6631. $ldd = find_first_file($cfg->{settings}{ldd}) || "/usr/bin/ldd";
  6632. unless (-x $ldd) { fatal_error "Can't find ldd."; }
  6633.  
  6634. $logger = find_first_file($cfg->{settings}{logger}) || "/bin/logger";
  6635. unless (-x $logger) { fatal_error "Can't find logger."; }
  6636.  
  6637. 1;
  6638.  
  6639.